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 intoControllerand spread further.Yes, if given
constexprarguments, the function will be executed at compile-time. Look at it like advanced constant folding. Furthermoreconstexprmethods used in compile-time context either cannot access*thisor it has too be constexpr. In particular,constexprmethod can only change the state ofconstexprinstance 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
constexpris probably not needed and it won't help anything.EDIT
constexprbehaviour example