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

时间:2017-08-01 06:51:49

标签: 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);
}

clang 4.0.1给出了以下错误:

main.cpp:6:17: error: non-type template argument is not a constant expression
    return foo<(args + ...)>;
                ^~~~
这让我感到惊讶。每个参数在编译时都是已知的,sum被标记为constexpr,因此我认为没有理由不能在编译时对fold表达式求值。

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

constexpr int result = (args + ...); // in sum

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

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

他们是否真的被允许?

3 个答案:

答案 0 :(得分:12)

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

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

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

非类型模板参数的参数必须是一个常量表达式,但x不是。因此,注释掉的线路形成不良。

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

答案 1 :(得分:5)

这个问题的一些读者可能有兴趣了解如何修改OP示例以便按预期编译和运行,因此我将这个附录包含在Brian:s excellent accepted answer中。

正如Brian所描述的,variadic函数参数的值不是sum中的常量表达式(但只要参数不“foo,就不会导致foo不是常量表达式”转义“foo(42)的范围;因为它是在常量表达式constexpr中创建的。

要将此知识应用于OP示例,而不是使用在转义constexpr的{​​{1}}直接范围时不会被视为sum的可变参数函数参数,我们可以将variadic函数参数迁移为可变的非类型模板参数。

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);
}

答案 2 :(得分:3)

您的问题与...无关。

template<class T0, class T1>
constexpr int sum(T0 t0, T1 t1) {
  return foo<(t0+t1)>;
}

这也以同样的方式失败。

从本质上讲,你的问题是constexpr函数必须可以用非constexpr参数调用。

constexpr意味着什么是常见的误解:它并不意味着总是constexpr&#34;。

有一些复杂的标准条款说明这里出了什么问题,但实质上是在constexpr函数中,函数参数本身不被视为constexpr。函数的结果可以是,如果输入是,但在函数内代码必须有效,即使参数不是constexpr

您仍然可以解决此问题:用户定义一个文字""_k,它解析整数并生成integral_constant

static_assert(sum(10_k, 1_k) == 11);

会编译并运行,因为积分常数上的+并不依赖于constexpr变量。或者,您可以将值作为非类型模板参数。     static_assert(sum&lt; 10,1&gt;()== 11);