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

时间:2017-12-07 13:56:09

标签: c++ lambda c++14 language-lawyer

以下代码被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{});
}

微软:

<source>(12): error C2131: expression did not evaluate to a constant

英特尔:

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

如果我将f(x)替换为f(decltype(x){}),微软和英特尔都不会抱怨。我理解x不是常量表达式,但它不在f中使用。这可能是GCC和clang不抱怨的原因。

我猜微软和英特尔编译器拒绝此代码是正确的。你觉得怎么样?

2 个答案:

答案 0 :(得分:5)

来自[expr.const]

  

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

     
      
  • [...]
  •   
  • 左值到左值的转换,除非它应用于

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

  •   

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

这似乎使这不是核心常数表达。

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

答案 1 :(得分:2)

这不是gcc / clang错误。可以使用模板函数在C ++ 11中重现相同的行为:

template <typename T>
void foo(T x)
{
    constexpr int e = f(x);
}

int main()
{
    foo(S{});
}

on godbolt.org

问题是,给定......

template <typename T>
void foo(T x)
{
    constexpr int e = f(x);
}

... f(x)是一个常量表达式吗?

来自[expr.const]

  

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

     
      
  • 为文字类,constexpr函数或简单析构函数的隐式调用调用constexpr构造函数以外的函数
  •   

S{}0是常量表达式,因为它不违反[expr.const]中的任何规则。 f(x)是一个常量表达式,因为它是对constexpr函数的调用。

除非我遗漏了某些内容,否则gcc和clang在这里是正确的。