I'm writting a simple C++ HTTP server framework. In my Server
class, I can add Route
's. Every route consists of a path, an HTTP method and a Controller
(which is a pipeline of functions to be called when the request was made.) That Controller
class is constructed by receiving a list of std::function
's (or, more precisely: std::function<void(const HTTPRequest&, HTTPResponse&, Context&)>
), but most of the time (or I should say every time), this Controller
will be initialized with a list of lambda function literals, as in the following code:
server.add_route("/", HTTPMethod::GET,
{
[](auto, auto& response, auto&) {
const int ok = 200;
response.set_status(ok);
response << "[{ \"test1\": \"1\" },";
response["Content-Type"] = "text/json; charset=utf-8";
},
[](auto, auto& response, auto&) {
response << "{ \"test2\": \"2\" }]";
},
}
);
Being this the case, I would like to make the add_route
function a constexpr
, because, correct me if I am wrong, constexpr
functions can be executed at compile time.
So, when I was making everything constexpr
, I found the following error:
Controller.cpp:9:1 constexpr constructor's 1st parameter type 'Callable' (aka 'function<void (const HTTPRequest &, HTTPResponse &, Context &)>') is not a literal type
What I want to know is: why std::function
's can't be literal types? Is there any way to circumvent this limitation?
Below is the code for Controller
class. I'm aware that there are still other compile errors, but this is the main issue I'm tackling right now. Thanks in advance!
controller.hpp
#pragma once
#include <functional>
#include <initializer_list>
#include <vector>
#include "context.hpp"
#include "httprequest.hpp"
#include "httpresponse.hpp"
typedef std::function<void(const HTTPRequest&, HTTPResponse&, Context&)> Callable;
template <size_t N>
class Controller {
private:
std::array<Callable, N> callables;
public:
static auto empty_controller() -> Controller<1>;
constexpr explicit Controller(Callable);
constexpr Controller();
constexpr Controller(std::initializer_list<Callable>);
void call(const HTTPRequest&, HTTPResponse&, Context&);
};
controller.cpp
#include "controller.hpp"
template <size_t N>
auto Controller<N>::empty_controller() -> Controller<1> {
return Controller<1>([](auto, auto, auto) {});
}
template <>
constexpr Controller<1>::Controller(Callable _callable) :
callables(std::array<Callable, 1> { std::move(_callable) }) { }
template <>
constexpr Controller<1>::Controller() :
Controller(empty_controller()) { }
template <size_t N>
constexpr Controller<N>::Controller(std::initializer_list<Callable> _list_callables) :
callables(_list_callables) { }
template <size_t N>
void Controller<N>::call(const HTTPRequest& req, HTTPResponse& res, Context& ctx) {
for (auto& callable : callables) {
callable(req, res, ctx);
}
}
Because it uses type erasure in order to accept any callable. This requires polymorphism which cannot be constexpr until C++20 which will allow
constexpr virtual
. You could use templates and capture the callable directly, but its type will creep intoController
and spread further.Yes, if given
constexpr
arguments, the function will be executed at compile-time. Look at it like advanced constant folding. Furthermoreconstexpr
methods used in compile-time context either cannot access*this
or it has too be constexpr. In particular,constexpr
method can only change the state ofconstexpr
instance at compile time. Otherwise the function is run ordinarly at runtime.The last point is relevant to you, running a HTTP server at compile-time hardly makes sense, so
constexpr
is probably not needed and it won't help anything.EDIT
constexpr
behaviour example