为什么折叠表达式不能出现在常量表达式中?

Rak*_*111 17 c++ c++17

请考虑以下代码:

template<int value>
constexpr int foo = value;

template<typename... Ts>
constexpr int sum(Ts... args) {
    return foo<(args + ...)>;
}

int main() {
    static_assert(sum(10, 1) == 11);
}
Run Code Online (Sandbox Code Playgroud)

clang 4.0.1给出了以下错误:

main.cpp:6:17: error: non-type template argument is not a constant expression
    return foo<(args + ...)>;
                ^~~~
Run Code Online (Sandbox Code Playgroud)

这让我感到惊讶.每个参数在编译时都是已知的,sum标记为constexpr,所以我看不出为什么在编译时无法计算fold表达式.

当然,这也会失败,并显示相同的错误消息:

constexpr int result = (args + ...); // in sum
Run Code Online (Sandbox Code Playgroud)

[expr.prim.fold] 不是很有用,它很短,只描述了允许的语法.

尝试更新版本的clang也会产生与gcc相同的结果.

它们实际上是否被允许?

Bri*_*ian 12

允许常量表达式包含折叠表达式.它允许使用的功能参数的值,除非该函数调用是本身整个常量表达式的一部分.举例来说:

constexpr int foo(int x) {
    // bar<x>();  // ill-formed
    return x;  // ok
}
constexpr int y = foo(42);
Run Code Online (Sandbox Code Playgroud)

y需要使用常量表达式初始化变量.foo(42)是一个可接受的常量表达式,因为即使调用foo(42)涉及对参数执行左值到右值的转换x以返回其值,该参数也是整个常量表达式中创建的,foo(42)因此其值是静态已知的.但x它本身并不是一个持续的表达foo.在其出现的上下文中不是常量表达式的表达式仍然可以是较大常量表达式的一部分.

非类型模板参数的参数必须是一个常量表达式,但x不是.所以注释掉的线路是不正确的.

同样,你(args + ...)不能成为一个常量表达式(因此不能用作模板参数),因为它对参数执行左值到右值的转换sum.但是,如果sum使用常量表达式参数调用函数,则函数调用作为一个整体可以是常量表达式,即使它(args + ...)出现在其中也是如此.


dfr*_*fri 5

这个问题的一些读者可能有兴趣了解如何修改OP示例以便按预期编译和运行,因此我将这个附录包含在Brian中:这是一个很好的接受答案.

正如Brian所描述的那样,variadic函数参数的值不是一个常量表达式sum(但不会导致foo不是常量表达式,只要参数不"逃避"范围foo;因为它已在常数表达式foo(42)).

要应用这些知识OP:为榜样,而不是使用,不会被当作一个可变参数函数参数constexpr逃离时,constexpr立即范围sum,我们可能会迁移可变参数函数的参数是一个可变参数非类型模板参数.

template<auto value>
constexpr auto foo = value;

template<auto... args>
constexpr auto sum() {
    return foo<(args + ...)>;
}

int main() {
    static_assert(sum<10, 1, 3>() == 14);
}
Run Code Online (Sandbox Code Playgroud)