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

  • 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]). ...