编译器无法执行 constexpr 表达式

Compiler can't execute constexpr expression

我有这样的代码:

template<typename ... Args>
constexpr size_t get_init_size(Args ... args) {
    return sizeof...(Args);
}

template<typename ... Args>
constexpr auto make_generic_header(Args ... args) {
    constexpr size_t header_lenght = get_init_size(args...);
    return header_lenght;
}

constexpr auto create_ipv4_header() {
    constexpr auto x = make_generic_header(0b01, 0b10, 0b01);
    return x;
}

我知道这是伪代码,但我将其隔离以查找错误。

编译器报错(GCC):

In instantiation of 'constexpr auto make_generic_header(Args&& ...) [with Args = {int, int, int}]':
/tmp/tmp.CaO5YHcqd8/network.h:39:43:   required from here
/tmp/tmp.CaO5YHcqd8/network.h:31:22: error: 'args#0' is not a constant expression
   31 |     constexpr size_t header_lenght = get_init_size(args...);
      |                      ^~~~~~~~~~~~~

我尝试将限定符 const 添加到函数参数中,但它同样不起作用。理论上所有这些函数都可以在编译时计算。但是凭我的知识找不到问题在哪里。

不是引用问题。 constexpr变量和constexpr函数是不同的东西。引用自回答:

The reference does not have a preceding initialization from the point of view of i, though: It's a parameter. It's initialized once ByReference is called.

This is fine, since f does have preceding initialization. The initializer of f is a constant expression as well, since the implicitly declared default constructor is constexpr in this case (§12.1/5). Hence i is initialized by a constant expression and the invocation is a constant expression itself.

关于“初始化前”,引自

It does mean "be initialized", but it's more importantly about the visibility of a preceding initialization within the context of the expression being evaluated. In your example, in the context of evaluating func(0) the compiler has the context to see the initialization of rf with 0. However, in the context of evaluating just the expression rf within func, it doesn't see an initialization of rf. The analysis is local, as in it doesn't analyze every call-site. This leads to expression rf itself within func not being a constant expression while func(0) is a constant expression.

对应你的情况,行:

constexpr size_t header_length = get_init_size(args...);

header_length的角度来看,get_init_size(args...)不是核心常量表达式,因为它的invoke参数来自函数的参数,args也不是核心常量表达式。

简单的修改就可以让你的代码正常工作,你可以去掉header_length中的constexpr限定符,或者直接make_generic_header中的return get_init_size(args...);函数体。

希望this and 也能帮到你。

constexpr 变量与函数的含义不同。

对于变量来说,就是说变量必须是编译时的。所以,需要用常量表达式来初始化。

对于函数,constexpr 意味着函数可以在编译时 运行 除了在 运行 时使用内部相同的代码。因此,您在内部所做的一切都必须适用于 运行时间调用。

考虑到这一点,让我们研究一下make_generic_header:

template<typename ... Args>
constexpr auto make_generic_header(Args ... args) {
    constexpr size_t header_lenght = get_init_size(args...);
    return header_lenght;
}

这里,header_lenght是一个constexpr变量,所以必须是编译时的。因此,get_init_size(args...) 也必须在编译时完成。但是,由于各种原因,参数不是常量表达式,所以这行不通。如果这确实有效,那就意味着 make_generic_header,一个 constexpr 函数,在 运行 时间不可用,这不符合它在编译时和 运行时间。

解决方法相当简单:使用适用于两种情况的代码:

template<typename ... Args>
constexpr auto make_generic_header(Args ... args) {
    size_t header_lenght = get_init_size(args...);
    return header_lenght;
}

你发现了吗?唯一的变化是从 header_lenght 中删除 constexpr。如果这个函数在编译时是运行,它仍然可以运行并且整个函数调用表达式将是一个常量表达式。


¹“但它在 consteval 中也不起作用!” - 是的,答案中给出的推理足以 constexpr,但我忽略了更根本的原因,因为它与此处无关。

我重写了可以工作的代码,我认为会在编译时执行:

template<typename ... Args>
constexpr auto make_generic_header(const Args ... args) {
    std::integral_constant<size_t, sizeof...(Args)> header_lenght;
    return header_lenght.value;
}

constexpr auto create_ipv4_header() {
    constexpr auto x = make_generic_header(0b01, 0b10, 0b01);
    return x;
}

我删除了函数 get_init_size 并使用了模板参数的代码部分(保证它将在编译时执行)并且在 return 之后传递给函数的参数数量(对于std::integral_constant 对于所有对象值相同并且在编译时知道)