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

Posted

技术标签:

【中文标题】通用 lambda 及其作为常量表达式的参数【英文标题】:Generic lambda and its argument as constant expression 【发布时间】:2018-05-21 15:45:06 【问题描述】:

以下代码被 GCC 7.2 和 clang 5.0.0 接受,但被 Microsoft VS 2017 15.5.0 Preview 5 和 Intel C++ compiler 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(decltype(x)) 替换f(x),微软和英特尔都不会抱怨。我了解x 不是常量表达式,但在f 内部没有使用。这可能是 GCC 和 clang 不抱怨的原因。

我猜微软和英特尔的编译器拒绝这个代码是正确的。你怎么看?

【问题讨论】:

@RichardHodges, -std=c++14. 好的,现在我认为这是 gcc 和 clang 的一个错误,因为 c++14 不附带 constexpr lambda ......这从 c++17 开始可用 @W.F.,lambda 故意不是constexpr(在实际代码中不是)。 MSVC 还抱怨:“失败是由读取超出其生命周期的变量引起的” 并且 g++ 和 clang 抱怨未使用的变量。如果您从 lambda 中 return,它们会编译。 @Bob__ f(x) 应该用在需要常量表达式的上下文中。当您执行return f(x) 时,您不会在编译时强制评估f(x) 【参考方案1】:

来自[expr.const]:

表达式 e 是一个核心常量表达式,除非根据抽象机的规则对 e 的求值将求值以下表达式之一:

[...]

左值到右值的转换,除非它被应用于

整数或枚举类型的非易失性左值,它指的是一个完整的非易失性 const 对象,该对象具有前面的初始化、使用常量表达式初始化或 引用字符串字面量子对象的非易失性泛左值,或 一个非易失性左值,它引用一个用 constexpr 定义的非易失性对象,或引用此类对象的一个​​非可变子对象,或 文字类型的非易失性左值,它引用一个非易失性对象,其生命周期在 e 的评估中开始;

[...]

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

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

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

【讨论】:

“在f(x) 中,我们对x 进行左值到右值的转换”我认为这是错误的。由于S 为空,它的复制构造函数永远不会对源左值执行左值到右值的转换。非常量表达式 S 的副本仍然可以是常量表达式 (godbolt.org/g/m4jSgz)。 constexpr 有时很疯狂。 @Casey 我认为这实际上是正确的解释。确实很疯狂。【参考方案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 构造函数之外的函数,或对普通析构函数的隐式调用

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

除非我遗漏了什么,否则 gcc 和 clang 在这里是正确的。

【讨论】:

以上是关于通用 lambda 及其作为常量表达式的参数的主要内容,如果未能解决你的问题,请参考以下文章

C++细节满满地lambda表达式讲解!

C++细节满满地lambda表达式讲解!

为啥在未计算的操作数中不允许使用 lambda 表达式,但在常量表达式的未计算部分中允许使用 lambda 表达式?

LibreOffice,使用常量作为查询参数

细谈字符串及其格式化表达式

python几个重要的函数(lambda,filter,reduce,map,zip)