Тип параметра конструктора 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;
}