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

2021-12-23 00:00:00 lambda c++ c++11 constant-expression c++14

如果我们查看 草案 C++ 标准 部分 5.1.2 Lambda 表达式 段落 2 说(强调我的未来):<块引用>

对 lambda 表达式的求值产生一个 prvalue 临时值 (12.2).这个临时对象称为闭包对象.lambda 表达式不得出现在未计算的操作数中(第 5 条).[注意:闭包对象的行为类似于函数对象(20.8).-尾注]

和部分 5.19 常量表达式 段落 2 说:

<块引用>

条件表达式是核心常量表达式,除非它涉及以下之一作为潜在评估的子表达式(3.2),但逻辑AND(5.14),逻辑OR(5.15) 和未评估的条件 (5.16) 操作不考虑 [...]

并具有以下项目符号:

<块引用>

――一个 lambda 表达式 (5.1.2);

那么为什么 lambda 表达式不允许出现在未计算的操作数中,而允许出现在常量表达式的未计算部分?

我可以看到在几种情况下未计算的操作数的类型信息如何(decltype 或 typeid) 不是很有用,因为每个lambda 有一个独特的类型.虽然我们为什么要在常量表达式的未评估上下文中允许它们尚不清楚,但也许允许 SFINAE?

解决方案

未计算操作数排除的核心原因在 C++ 标准核心语言缺陷报告和已接受的问题 #1607.模板参数中的 Lambdas 旨在澄清此限制,并说明 5.1.2 部分中限制的意图是:

<块引用>

[...] 避免在函数模板签名中处理它们的需要 [...]

作为问题文档,当前的措辞实际上有一个漏洞,因为常量表达式允许在未评估的上下文中使用它们.但它并没有直接说明这种限制的基本原理.避免名称修改的愿望很突出,您可以推断出避免扩展 SFINAE 也是需要的,因为提议的决议旨在收紧限制,即使有几个可行的替代方案允许 SFINAE>.修改后的5.1.2段2如下:

<块引用>

lambda 表达式不得出现在未计算的操作数(第 5 条 [expr])、模板参数、别名声明、typedef 声明或函数声明中,或函数体外部的函数模板和默认参数 [注意:目的是防止 lambda 出现在签名中 - 尾注].[注意:闭包对象的行为类似于函数对象 (20.10 [function.objects]).――结尾说明]

此提议已被接受,并在 N3936 中(请参阅此答案以获取链接a>)

更明确地讨论避免将 lambdas 作为未计算操作数的基本原理.题为 不允许 lambda 表达式的基本原理的讨论comp.lang.cpp.moderated Daniel Krügler 上未评估的上下文列出了三个原因:

  1. 可能的SFINAE案例的极端扩展:

<块引用>

[...]他们被排除在外的原因正是由于 sfinae 案例的这种极端扩展(您为编译器打开了一个潘多拉盒子)[...]

  1. 在许多情况下它是无用的,因为每个 lambda 都有一个独特的类型,给出的假设示例:

    templatevoid g(T, U, decltype([](T x, T y) { return x + y; }) func);g(1, 2, [](int x, int y) { return x + y; });

    声明和调用中 lambda 的类型不同(根据定义),因此这行不通.

  2. 名称修改 也会成为一个问题,因为一旦您允许 lambda 在函数签名中,lambda 的主体也必须被破坏.这意味着要制定规则来处理每个可能的语句,这至少对某些实现来说会很麻烦.

If we look at the draft C++ standard section 5.1.2 Lambda expressions paragraph 2 says (emphasis mine going forward):

The evaluation of a lambda-expression results in a prvalue temporary (12.2). This temporary is called the closure object. A lambda-expression shall not appear in an unevaluated operand (Clause 5). [ Note: A closure object behaves like a function object (20.8).―end note ]

and section 5.19 Constant expressions paragraph 2 says:

A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression (3.2), but subexpressions of logical AND (5.14), logical OR (5.15), and conditional (5.16) operations that are not evaluated are not considered [...]

and has the following bullet:

― a lambda-expression (5.1.2);

So why are lambdas expressions not allowed in an unevaluated operand but are allowed in the unevaluated portions of constant expressions?

I can see how for unevaluated operands the type information in several cases(decltype or typeid) is not very useful since each lambda has a unique type. Although why we would want to allow them in the unevaluated context of a constant expression is not clear, perhaps to allow for SFINAE?

解决方案

The core reason for the unevaluated operands exclusion is covered in C++ Standard Core Language Defect Reports and Accepted Issues #1607. Lambdas in template parameters which seeks to clarify this restriction and states the intention of the restriction in section 5.1.2 was to:

[...] avert the need to deal with them in function template signatures [...]

As the issue documents the current wording actually has a hole since constant expressions allows them in an unevaluated context. But it does not outright state the rationale for this restriction. The desire to avoid name mangling stands out and you can infer that avoiding extending SFINAE was also desired since the proposed resolution seeks to tighten the restriction even though several viable alternatives would have allowed SFINAE. The modified version of 5.1.2 paragraph 2 as follows:

A lambda-expression shall not appear in an unevaluated operand (Clause 5 [expr]), in a template-argument, in an alias-declaration, in a typedef declaration, or in the declaration of a function or function template outside its function body and default arguments [Note: The intention is to prevent lambdas from appearing in a signature ―end note]. [Note: A closure object behaves like a function object (20.10 [function.objects]). ―end note]

This proposal was accepted and is in N3936(see this answer for a link)

For a more explicit discussion of the rationale to avoid having lambdas as an unevaluated operand. The discussion titled Rationale for lambda-expressions not being allowed in unevaluated contexts on comp.lang.cpp.moderated Daniel Krügler lays out three reasons:

  1. The extreme extension of possible SFINAE cases :

[...]The reason why they became excluded was due to exactly this extreme extension of sfinae cases (you were opening a Pandora box for the compiler)[...]

  1. In many cases it is just useless since each lambda has a unique type, the hypothetical example given:

    template<typename T, typename U>
    void g(T, U, decltype([](T x, T y) { return x + y; }) func);
    
    g(1, 2, [](int x, int y) { return x + y; });
    

    The type of the lambda in the declaration and the call are different(by definition) and therefore this can not work.

  2. Name mangling also becomes a problem since once you allow a lambda in a function signature the bodies of the lambda will have to be mangled as well. This means coming up with rules to mangle every possible statement, which would burdensome for at least some implementations.

相关文章