通用的lambda及其作为恒定表达的论点

Generic lambda and its argument as constant expression

本文关键字:lambda      更新时间:2023-10-16

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

Microsoft:

<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(decltype(x){})替换f(x),Microsoft和Intel都不会抱怨。我知道x不是恒定的表达式,而是在f中使用。这可能就是GCC和Clang不抱怨的原因。

我想Microsoft和Intel编译器在拒绝此代码方面是正确的。你怎么看?

来自[expr.const]:

表达式E是A 核心常数表达式,除非遵循e的评估E遵循抽象机器的规则,将评估以下表达式之一:

  • [...]

  • 除非将其应用于

    ,否则
    • 积分或枚举类型的非易失性glvalue,它是指具有前面初始化的完整的非挥发性const对象,以恒定表达式初始化,或者
  • 一个非易失性的glvalue,它是指字符串的子对象,或
  • 是指使用ConstexPR定义的非易失性对象,或者是指该对象的不可弹性子对象,或
  • 字面类型的非易失性glvalue是指非易失性物体,其寿命开始于评估e;
  • [...]

  • f(x)中,我们在x上进行lvalue-to-rvalue转换。x不是积分或枚举类型,它不是字符串字符TER的子对象,它不是由ConstexPR定义的对象,其寿命不是从评估f(x)开始的。

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

    但是,正如Casey指出的那样,由于S是空的,因此其隐式生成的复制构造函数实际上不会触发此lvalue to-rvalue转换。这意味着该表达式中没有什么实际上违反了任何核心恒定表达限制,因此GCC和Clang在接受它时是正确的。这种解释对我来说似乎是正确的。constexpr很有趣。

    这不是gcc/clang错误。具有模板函数的C 11中可以复制相同的行为:

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

    在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在这里是正确的。