Can lambdas be used as non-type template parameter?

荒凉一梦 提交于 2020-07-17 11:14:19

问题


Is the following code legal?

template <auto Lambda>
struct A {};

int main () {
  auto lmb = [](int i){return i*i;};
  A<lmb> a;
  return 0;
}

I noticed that g++ compiles it fine, while clang++ returns error: a non-type template parameter cannot have type '(lambda at main.cpp:...)'.


回答1:


Can lambdas be used as non-type template parameter?

Yes, with implementations that has implemented P0732R2 - Class types in non-type template parameters but clang++ has not implemented it yet.

Source: https://en.cppreference.com/w/cpp/compiler_support


Note that the lambda needs to be at least constexpr (which it is by default):

When this specifier is not present, the function call operator will be constexpr anyway, if it happens to satisfy all constexpr function requirements.

You can however add constexpr to get an error on the lambda itself instead of when using it as a template parameter. As a side note: You can also specify it to be consteval to make it work as a non-type template parameter.

A stateful lambda can be constexpr:

constexpr auto lmb1 = [](int i) {
    static int x = 0;
    return i*i + ++x;
};

while a lambda capturing by reference, or capturing by copy and mutating (mutable), can not. Capturing by copying a constexpr is ok though.

Generic lambdas may be constexpr too:

constexpr auto gen_lmb = []<typename T>(T& val) {
   val += val;
   return val;
};

template <auto Lambda>
struct A {
    template<typename T>
    void doit(T&& arg) {
        std::cout << Lambda(arg) << '\n';
    }
};

//...

A<gen_lmb> ginst;

int v = 1000;
ginst.doit(v);
ginst.doit(std::string("foo "));
std::cout << v << '\n';
2000
foo foo
2000



回答2:


[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;



回答3:


First, I think your lambda should be constexpr to be used as a non-type template parameter. I find it a bit weird, that it works.

But then it should work in this case. The Standard tells us that a non-type template parameter can be a literal class type (that's a bit iffy since closures are literal but not really class types, I think they are explicitly included here) with the additional requirements that

  • All base clases and non-static members must be non-mutable and public
  • Their types must be structural or arrays thereof

So we don't have problems in this easy example. But if you capture anything, your lambda has a non-public member variable and should be out. If that is not so sharp for closures, it definitely stops working if you capture something non-constexpr.



来源:https://stackoverflow.com/questions/62324050/can-lambdas-be-used-as-non-type-template-parameter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!