Why are lambda expressions not allowed in an unevaluated operands but allowed in the unevaluated portions of constant expressions?

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?


Solution 1:

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.