[temp.arg.nontype]/1:
If the type T of a template-parameter contains a placeholder type ([dcl.spec.auto]) or a placeholder for a deduced class type
([dcl.type.class.deduct]), the type of the parameter is the type
deduced for the variable x in the invented declaration
T x = template-argument ;
If a deduced parameter type is not permitted for a
template-parameter declaration ([temp.param]), the program is
ill-formed.
So, the rules are set by [temp.param]/6:
A non-type template-parameter shall have one of the following
(possibly cv-qualified) types: ...
(6.1) a structural type ...
The rules for structural type are: --my emphasis--
(7.1) a scalar type, or
(7.2) an lvalue reference type, or
(7.3) a literal class type with the following properties:
(7.3.1) all base classes and non-static data members are public and non-mutable and
(7.3.2) the types of all bases classes and non-static data members are structural types or (possibly multi-dimensional) array thereof.
Since the lambda has no base class, the only requirement is that it has to be a literal class type ([basic.types]) which includes:
(10.5.2) ... a closure type ([expr.prim.lambda.closure]) ...
The data members of a structural type shall also be structural type, this applies to the lambda's capture in this case, as long as all its members are public and non-mutable.
@Nicol Bolas commented below that a lambda with captures, even if constexpr literal type captures, is not mandated by the standard to manage the captures as public fields.
The bottom line is that in C++20 a constexpr lambda expression without a capture shall be legal as a template non-type argument (based on [basic.types]/10.5.2 mentioned above).
See also an answer by @Barry to a similar question.
Below code compiles with gcc, but as I understand from the comment by Nicol Bolas, not all cases are guaranteed by the spec (or even worse, all cases are not guaranteed by the spec?).
Suppose we have:
template <auto T> struct A {};
struct B {};
struct C {
~C(){}
};
Literal type lambdas, that shall be legal as template arguments:
// compiles in gcc and should be ok by the spec as of [basic.types]/10.5.2
A<[](){}> a; // compiler deduces the anonymous lambda to be constexpr
auto lmb1 = [](){};
// same as above
A<lmb1> a1;
// compiler deduces lmb1 above to be constexpr
// same as it will deduce the following:
B b {};
A<b> here_i_am;
Lambdas, that are compiled by gcc as template arguments, but as Nicol Bolas argues in the comment - the spec doesn't guarantee them to be literal types:
const int i = 0;
constexpr auto lmb2 = [i](){};
// compiles in gcc but is not guaranteed by the spec
A<lmb2> a2;
constexpr auto lmb3 = [b](){}; // B is literal
// compiles in gcc but is not guaranteed by the spec
A<lmb3> a3;
Non-literal type lambdas, not legal as template arguments:
const int j = 0;
// below doesn't compile: <lambda()>{j} is not a constant expression
constexpr auto lmb4 = [&j](){}; // local reference - not constexpr
A<lmb4> a4;
C c;
// below doesn't compile: <lambda()>'{c} does not have 'constexpr' destructor
constexpr auto lmb5 = [c](){}; // C is not literal
A<lmb5> a5;
constexpr auto= ...
here. What about the other compilers? Onlyconstexpr
s can be template parameters (incl.auto
). – Merta