Search code examples
c++templateslambdalanguage-lawyerc++20

Is a lambda expression a legal default (non-type template) argument?


All standard references below refers to N4861 (March 2020 post-Prague working draft/C++20 DIS).


Background

The Q&A Are captureless lambdas structural types? highlighted the potential underspecification of whether certain lambda-expressions have associated closure types that are (literal and) structural types or not. The impact of this classification could allow or reject using a closure type as non-type template parameter; essentially passing structural type lambdas as non-type template parameters.

template<auto v>
constexpr auto identity_v = v;

constexpr auto l1 = [](){};
constexpr auto l2 = identity_v<l1>;

Now, according to [expr.prim.lambda.closure]/1 the type of each lambda-expression is unique

[...] a unique, unnamed non-union class type, called the closure type [...]

On the other hand, [basic.def.odr]/1 [extract, emphasis mine] states

No translation unit shall contain more than one definition of any variable, function, class type, enumeration type, template, default argument for a parameter (for a function in a given scope), or default template argument.

arguably meaning that default template arguments are considered definitions that need to respect the ODR.

Question

... which leads to my question:

  • Is a lambda expression a legal default (non-type template) argument and, if so, wouldn't this imply that each instantiation using such a default argument instantiates a unique specialization?

(Please highlight also if near-illegal: e.g. if anything beyond a single instantiation would lead to an ODR-violation).


Why?

If this is in fact legal, each invocation of say a variable template with a lambda as default argument would result in an instantiation of a unique specialization:

template<auto l = [](){}>
               // ^^^^^^ - lambda-expression as default argument
constexpr auto default_lambda = l;

static_assert(!std::is_same_v<
    decltype(default_lambda<>),
    decltype(default_lambda<>)>);

Both GCC (DEMO) and Clang (DEMO) accepts the program above

If the compilers are correct to accept this example, this means allowing another mechanism to capture and retrieve a meta-programming state, a technique that has since long been deemed, as per CWG open issue 2118, as

... arcane and should be made ill-formed.


Solution

  • After CWG 2845 (resolved 2024-02-02): potentially - captureless lambdas ARE (once again) structural types

    As per of the resolution to CWG 2845, captureless a lambdas are structural types again, meaning there seems to be nothing prohibiting lambda expressions as a legal default (non-type template) argument.

    It may still be an open questions about how this opens up the non-intended abuse potential of stateful meta-programming becoming more wide-spread due to this new "tool".

    2845. Make the closure type of a captureless lambda a structural type

    [...]

    Proposed resolution (approved by CWG 2024-02-02):

    Change in 7.5.5.2 [expr.prim.lambda.closure] paragraph 3 as follows:

    The closure type is not an aggregate type (9.4.2 [dcl.init.aggr]) and not ; it is a structural type (13.2 [temp.param]) if and only if the lambda has no lambda-capture. An implementation may define the closure type differently from ...


    After CWG 2542 (resolved 2023-03-30), but prior to CWG 2845: no - captureless lambdas ARE NOT structural types

    As per the resolution of CWG 2542 it was made clear that lambdas are not structural types, no matter their capture. So for some time, there was a clear answer to this question:

    • (After CWG 2542 but before CWG 2845): No, it is not a legal default non-type template argument, as the closure type of lambdas are, as per the resolution of CWG 2542, not structural types.

    2542. Is a closure type a structural type?

    Consider:

    template <auto V>
    void foo() {}
    
    void bar() {
      foo<[i = 3] { return i; }>();
    }
    

    It is unclear whether the data members of a closure type are public or private. This makes a difference, since it affects whether a closure type is a structural type or not [...]

    [...]

    Proposed resolution (approved by CWG 2023-03-30):

    Change in 7.5.5.2 [expr.prim.lambda.closure] paragraph 2 as follows:

    ... The closure type is not an aggregate type (9.4.2 [dcl.init.aggr]) and not a structural type (13.2 [temp.param]). ...