通用lambda及其参数作为常量表达式

Evg*_*Evg 7 c++ lambda language-lawyer c++14

以下代码被GCC 7.2和clang 5.0.0接受,但被Microsoft VS 2017 15.5.0 Preview 5和Intel C++编译器19拒绝:

struct S { };

constexpr int f(S)
{
    return 0;
}

int main()
{
    auto lambda = [](auto x)
    {
        constexpr int e = f(x);
    };

    lambda(S{});
}
Run Code Online (Sandbox Code Playgroud)

微软:

<source>(12): error C2131: expression did not evaluate to a constant
Run Code Online (Sandbox Code Playgroud)

英特尔:

<source>(12): error: expression must have a constant value
    constexpr int e = f(x);
                      ^
<source>(12): note: the value of parameter "x" (declared at line 10) cannot be used as a constant
    constexpr int e = f(x);
                        ^
Run Code Online (Sandbox Code Playgroud)

如果我更换f(x)f(decltype(x){}),微软和英特尔都不要抱怨.我明白这x不是一个常量表达式,但它不在内部使用f.这可能是GCC和clang不抱怨的原因.

我猜微软和英特尔编译器在拒绝此代码方面是正确的.你怎么看?

Bar*_*rry 5

来自[expr.const]:

表达式e是核心常量表达式,除非根据抽象机器的规则评估e将评估以下表达式之一:

  • [...]
  • 除非适用,否则左值转换为左值

    • 一个非整数或枚举类型的非易失性glvalue,它引用一个完整的非易失性const对象,具有前面的初始化,用常量表达式初始化,或者
    • 一个非易失性glvalue,引用字符串文字的子对象,或
    • 非易失性glvalue,引用constexpr定义的非易失性对象,或引用此类对象的不可变子对象,或者
    • 文字类型的非易失性glvalue,指的是一个非易失性对象,其生命周期始于e的评估范围内;
  • [...]

f(x),我们进行左值到右值的转换x.x它不是整数或枚举类型,它不是字符串文字的子对象,它不是用constexpr定义的对象,它的生命周期不是从评估开始的f(x).

这似乎使这不是一个核心的常数表达.

但是,正如Casey指出的那样,因为它S是空的,所以它隐式生成的复制构造函数中的任何内容都不会实际触发这个左值到右值的转换.这意味着此表达式中的任何内容实际上都没有违反任何核心常量表达式限制,因此gcc和clang在接受它时是正确的.这种解释对我来说似乎是对的.constexpr有趣.

  • "在`f(x)`中,我们对`x`进行左值到右值的转换"我认为这是错误的.由于`S`为空,因此其复制构造函数永远不会对源左值执行左值到右值的转换.非常量表达式"S"的副本仍然可以是常量表达式(https://godbolt.org/g/m4jSgz).`constexpr`有时候很疯狂. (3认同)