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);
}
}
why std::function's can't be literal types? Is there any way to circumvent this limitation?
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 into Controller
and spread further.
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.
Yes, if given constexpr
arguments, the function will be executed at compile-time. Look at it like advanced constant folding. Furthermore constexpr
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 of constexpr
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.
constexpr
behaviour examplestruct Foo{
//If all members are trivial enough and initialized, the constructor is constexpr by default.
int state=10;
//constexpr Foo()=default;
constexpr int bar(bool use_state){
if(use_state)
return state++;
else
return 0;// Literal
}
constexpr int get_state()const{
return state;
}
};
template<int arg>
void baz(){}
int main(int argc, char* argv[])
{
Foo foo;
//Carefull, this also implies const and ::bar() is non-const.
constexpr Foo c_foo;
foo.bar(true);//Run-time, `this` is not constexpr even though `true` is
foo.bar(false);//Compile-time, `this` was not needed, `false` is constexpr
bool* b = new bool{false};
foo.bar(*b);//Always run-time since `*b` is not constexpr
//Force compile-time evaluation in compile-time context
//Foo has constexpr constructor, creates non-const (temporary) constexpr instance
baz<Foo().bar(true)>();
baz<Foo().bar(false)>();
baz<foo.bar(false)>();
//ERROR, foo is not constexpr
//baz<foo.bar(true)>();
//ERROR, c_foo is const
//baz<c_foo.bar(false)>();
//Okay, c_foo is constexpr
baz<c_foo.get_state()>();
//ERROR, foo is not constexpr
//baz<foo.get_state()>();
return 0;
}