All standard references below refers to N4861 (March 2020 post-Prague working draft/C++20 DIS).
In the Q&A Are captureless lambdas structural types? it was made clear that certain lambda-expressions have associated closure types that are (literal and) structural types, such that a particular such closure type may be used 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.
... which leads to my question:
(Please highlight also if near-illegal: e.g. if anything beyond a single instantiation would lead to an ODR-violation).
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.
A non-type template argument provided within a template argument list is an expression whose value can be determined at compile time. Such arguments must be constant expressions, addresses of functions or objects with external linkage, or addresses of static class members.
Lambda-expressions are not allowed in unevaluated expressions, template arguments, alias declarations, typedef declarations, and anywhere in a function (or function template) declaration except the function body and the function's default arguments.
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?
Yes, it means that given your templated variable:
template<auto l = [](){}>
constexpr auto default_lambda = l;
every call to default_lambda
will produce a new template instantiation by generating a new unique closure type and thus provides a new way to capture a metaprogramming state.
Thus, every expressions that use default_lambda
have to be reevaluated. If the state of the compiler changed since the last instantiation and if we used it in a dependent expression (ex: check that a type is defined) then the result of the expression might change.
For example:
struct X; // not defined
template <typename T, auto = default_lambda<>>
consteval bool is_defined() {
if constexpr (requires { T{}; }) {
return true;
} else {
return false;
}
}
static_assert(is_defined<X>() == false);
struct X {};
static_assert(is_defined<X>() == true);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With