【问题标题】:constexpr constructor's parameter type 'std::function' is not a literal typeconstexpr 构造函数的参数类型 'std::function' 不是文字类型
【发布时间】:2020-12-25 08:14:39
【问题描述】:

我正在编写一个简单的 C++ HTTP 服务器框架。在我的Server 课程中,我可以添加Route。每个路由都包含一个路径、一个 HTTP 方法和一个 Controller(这是在发出请求时调用的函数管道。)Controller 类是通过接收 std::function 的列表(或者,更准确地说:std::function<void(const HTTPRequest&, HTTPResponse&, Context&)>),但大多数时候(或者我应该说每次),这个Controller 将使用 lambda 函数文字列表进行初始化,如下所示代码:

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's 不能是文字类型?有没有办法绕过这个限制?

下面是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);
    }
}

【问题讨论】:

  • 是的,std::function 不是文字类型。例如它没有constexpr 构造函数。也许你可以传递一个函数指针,它应该可以工作。
  • 一开始我考虑过使用函数指针,但后来我发现它们不允许我让add_route 语法看起来像我所做的那样干净。除此之外,正如我从接受的答案中发现的那样,制作 add_route constexpr 可能甚至不可能,或者可能不是有用的优化,因为该方法必须改变 @987654345 的状态@类。

标签: c++ c++11 lambda constexpr constexpr-function


【解决方案1】:

为什么 std::function 不能是文字类型?有没有办法绕过这个限制?

因为它使用类型擦除来接受任何可调用的。这需要在 C++20 之前不能是 constexpr 的多态性,这将允许 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;
}

【讨论】:

  • 因此,即使add_route 中的每个参数都是constexpr,编译器也不允许我为该方法添加constexpr 说明符,因为它需要修改Server 对象?
  • @AndréWinston 它总是允许添加它,它只会在调用时回退到运行时执行。关键思想是constexpr 强制编译时执行仅在提供 constexpr 参数或在编译时上下文(其中参数也是 constexpr)中提供时。它不禁止像往常一样将执行推迟到运行时。状态访问是基于需要知道的。 IE。即使在编译时调用中,访问未采用分支中的状态仍然可以。我在答案中添加了一个示例。随意询问更多:)
  • 如果您有兴趣,请查看始终在编译时运行的 C++20 consteval。
猜你喜欢
  • 1970-01-01
  • 2012-05-30
  • 1970-01-01
  • 1970-01-01
  • 2016-02-07
  • 2020-12-13
  • 2019-07-05
  • 2010-12-05
  • 2012-09-22
相关资源
最近更新 更多