constexpr constructor's parameter type 'std::function' is not a literal type

985 views Asked by At

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);
    }
}
1

There are 1 answers

3
Quimby On BEST ANSWER

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.

EDIT constexpr behaviour example

struct 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;
}