Тип параметра конструктора constexpr 'std::function' не является буквальным типом

Я пишу простую структуру HTTP-сервера C++. В моем Server класс, я могу добавить Routeс. Каждый маршрут состоит из пути, метода HTTP и Controller (который представляет собой конвейер функций, вызываемых при выполнении запроса). Controller класс создается путем получения списка std::function(точнее: std::function<void(const HTTPRequest&, HTTPResponse&, Context&)>), но в большинстве случаев (или я должен сказать каждый раз) это Controller будет инициализирован списком литералов лямбда-функций, как в следующем коде:

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\" }]";
                    },
                }
        );

В этом случае я бы хотел add_route функция а constexprпотому что поправьте меня, если я ошибаюсь, constexpr функции могут выполняться во время компиляции.

Итак, когда я все делал constexpr, Я обнаружил следующую ошибку:

Controller.cpp:9:1 constexpr constructor's 1st parameter type 'Callable' (aka 'function<void (const HTTPRequest &, HTTPResponse &, Context &)>') is not a literal type

Я хочу знать: почему std::functionНе может быть буквального типа? Есть ли способ обойти это ограничение?

Ниже приведен код для Controllerкласс. Я знаю, что есть и другие ошибки компиляции, но это основная проблема, над которой я сейчас решаюсь. Заранее спасибо!

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 ответ

Решение

почему std::function не может быть буквальным типом? Есть ли способ обойти это ограничение?

Потому что он использует стирание типа, чтобы принимать любые вызываемые. Для этого требуется полиморфизм, который не может быть constexpr до C++20, который позволит constexpr virtual. Вы можете использовать шаблоны и напрямую захватывать вызываемый объект, но его тип будет проникать в Controller и распространяться дальше.

В этом случае я хотел бы сделать функцию add_route constexpr, потому что, поправьте меня, если я ошибаюсь, функции constexpr могут выполняться во время компиляции.

Да, если дано constexprаргументы, функция будет выполнена во время компиляции. Посмотрите на это как на продвинутое сворачивание констант. более того constexpr методы, используемые в контексте времени компиляции, либо не могут получить доступ *thisили это тоже должно быть constexpr. В частности, constexpr метод может только изменить состояние constexprэкземпляр во время компиляции. В противном случае функция запускается во время выполнения как обычно.

Последний пункт важен для вас, запуск HTTP-сервера во время компиляции вряд ли имеет смысл, поэтому constexpr наверное не нужен и ничего не поможет.

РЕДАКТИРОВАТЬ constexpr пример поведения

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;
}
Другие вопросы по тегам