C++20: Non-capturing lambda in non-type template parameter

家住魔仙堡 提交于 2020-06-25 02:41:13

问题


Does C++20 allow a non-capturing lambda decayed to a function pointer to be passed directly as a non-type template parameter? If so, what is the correct syntax?

I have tried the following code in various versions of clang and gcc using -std=c++2a.

#include <iostream>

template<auto f>
struct S {
    static void invoke(int x) { f(x); }
};

using X = S<+[](int x) -> void { std::cout << x << " hello\n"; }>;

int main()
{
    X::invoke(42);
}

gcc compiles the code without complaint and the code runs as expected.

clang fails compilation with the following error:

error: a lambda expression cannot appear in this context
using X = S<+[](int x) -> void { std::cout << x << " hello\n"; }>;
             ^

Here is the full code (online versions):

Clang 10.0.0 HEAD: https://wandbox.org/permlink/n5eKQ4kQqSpDpr4k

Gcc 10.0.0 HEAD 20200113: https://wandbox.org/permlink/vJ44sdMtwCKAFU64


回答1:


Does C++20 allow a non-capturing lambda decayed to a function pointer to be passed directly as a non-type template parameter?

Yes.

Indeed, you can go one step further - you don't even need to convert the lambda to a function pointer. You can just provide the lambda. This is valid C++20:

using Y = S<[](int x) -> void { std::cout << x << " hello\n"; }>;

The rule we have in C++20 is that lambdas are now allowed in unevaluated contexts (P0315). Among many other wording changes there, this paper struck the rule that prevented lambdas from being used in template arguments (C++17's [expr.prim.lambda]/2):

A lambda-expression shall not appear in an unevaluated operand, in a template-argument, in an alias-declaration, in a typedef declaration, or in the declaration of a function or function template outside its function body and default arguments.

That clause does not exist anymore in C++20.

Removing this restriction allows the lambda to be used as a template argument, and the conversion from captureless lambda to function pointer was already constexpr in C++17. clang simply does not implement this feature yet (using T = decltype([]{}); compiles on gcc, not yet on clang). I wouldn't call this a clang bug yet, it's just a clang not-yet-implemented-feature (lambdas in unevaluated contexts is not yet listed as implemented in the cppreference compiler support page).


C++20 non-type template parameters (P1907) allows even dropping the + because captureless lambdas count as structural types ([temp.param]/7) by way of simply not having any data members at all.




回答2:


If the rules on this haven't changed since C++17 then using a lambda as a template parameter is not allowed for the same reason that using a string literal is not allowed. Every lambda has a different type and every string literal refers to a different object. What changed in C++17 is that closure objects are now constexpr. To use a string literal or a lambda as a template parameter, the object must have external linkage. So this is allowed in C++17.

template <auto>
struct S {};

constexpr const char string[] = "String literal";
constexpr auto lambda = []{};

S<string> a;
S<+lambda> b;

The closure object itself cannot be used as a template parameter (so you can't do S<lambda>) but this might have changed in C++20 with three-way comparisons. The reason the objects must have external linkage is because it kind of breaks templates. S<+[]{}> and S<+[]{}> would be considered different types even though they look the same (similarly with S<"">).




回答3:


Template parameter must be a constexpr variable.

There is a relevant proposal N4487 for lambdas.

I don't know if it made it into C++20.



来源:https://stackoverflow.com/questions/59761488/c20-non-capturing-lambda-in-non-type-template-parameter

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