C++20:非类型模板参数中的非捕获 lambda
Posted
技术标签:
【中文标题】C++20:非类型模板参数中的非捕获 lambda【英文标题】:C++20: Non-capturing lambda in non-type template parameter 【发布时间】:2020-05-02 19:33:17 【问题描述】:C++20 是否允许将衰减为函数指针的非捕获 lambda 作为非类型模板参数直接传递?如果是这样,正确的语法是什么?
我使用-std=c++2a
在各种版本的clang和gcc中尝试了以下代码。
#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 无怨无悔地编译代码,代码按预期运行。
clang 编译失败并出现以下错误:
error: a lambda expression cannot appear in this context
using X = S<+[](int x) -> void std::cout << x << " hello\n"; >;
^
这是完整的代码(在线版本):
Clang 10.0.0 HEAD:https://wandbox.org/permlink/n5eKQ4kQqSpDpr4k
Gcc 10.0.0 HEAD 20200113:https://wandbox.org/permlink/vJ44sdMtwCKAFU64
【问题讨论】:
有什么进展吗?最新的苹果叮当声仍然是同样的抱怨。 【参考方案1】:C++20 是否允许将衰减为函数指针的非捕获 lambda 作为非类型模板参数直接传递?
是的。
确实,您可以更进一步——您甚至不需要将 lambda 转换为函数指针。您可以只提供 lambda。这是有效的 C++20:
using Y = S<[](int x) -> void std::cout << x << " hello\n"; >;
我们在 C++20 中的规则是,现在允许在未计算的上下文中使用 lambda (P0315)。在许多其他措辞更改中,本文提出了阻止 lambda 在模板参数中使用的规则 (C++17's [expr.prim.lambda]/2):
lambda-expression 不得出现在未计算的操作数中、模板参数、别名声明中,在 typedef 声明中,或者在函数体和默认参数之外的函数或函数模板的声明中。
该子句在 C++20 中不再存在。
移除这个限制允许 lambda 被用作模板参数,并且从无捕获 lambda 到函数指针的转换在 C++17 中已经是 constexpr。 clang 还没有实现这个特性(using T = decltype([]);
在 gcc 上编译,还没有在 clang 上编译)。我不会称这为 clang 错误,它只是一个 clang 尚未实现的功能(未评估上下文中的 lambdas 尚未在 cppreference compiler support page 中列为已实现)。
C++20 非类型模板参数 (P1907) 甚至允许删除 +
,因为无捕获 lambda 算作 结构类型 ([temp.param]/7),因为根本没有任何数据成员。
【讨论】:
标准在哪里说无捕获 lambda 没有任何数据成员? @Danra 该标准根本没有说明 lambda 有成员。 这是否意味着它可能具有非公共成员,使其无法传递给模板参数,具体取决于实现?作为上下文,我从***.com/questions/62324050/… 来到这里,IIUC,这就是 Nicol Bolas 在其中一个 cmets 中所声称的。 @Danra 我收回我说的话。该标准规定,对于通过副本捕获的每个实体,我们都会获得一个非静态数据成员,并且未指定我们是否获得通过引用捕获的实体的非静态数据成员。虽然它没有说我们不 为not 捕获的东西获取任何非静态数据成员,但这也是一个相当不利于用户的实现。为什么会有人这样做? 谢谢巴里。我不是说有人会,我想知道标准中是否有我错过的内容【参考方案2】:如果这方面的规则自 C++17 以来没有改变,则不允许使用 lambda 作为模板参数,原因与不允许使用字符串文字相同。每个 lambda 都有不同的类型,每个字符串字面量都指向不同的对象。 C++17 中的变化是闭包对象现在是constexpr
。要使用字符串文字或 lambda 作为模板参数,对象必须具有外部链接。所以这在 C++17 中是允许的。
template <auto>
struct S ;
constexpr const char string[] = "String literal";
constexpr auto lambda = [];
S<string> a;
S<+lambda> b;
闭包对象本身不能用作模板参数(所以你不能这样做S<lambda>
),但这可能在 C++20 中通过三向比较发生了变化。对象必须具有外部链接的原因是因为它破坏了模板。 S<+[]>
和 S<+[]>
将被视为不同的类型,即使它们看起来相同(与 S<"">
类似)。
【讨论】:
自 C++11 [temp.arg.nontype]/1.3 以来就不需要内部链接,所以虽然我自己很难找到 C++17 禁止这样做的确切位置,但我不认为链接是这里的解释…… 另外,请注意,字符串文字是否引用不同的对象是未指定的 [lex.string]/16。链接是名称[basic.link] 的属性。由于字符串文字没有名称,它们不能有链接……【参考方案3】:模板参数必须是constexpr
变量。
有一个针对 lambdas 的相关提案 N4487。
我不知道它是否进入了 C++20。
【讨论】:
这已被 C++17 采用。以上是关于C++20:非类型模板参数中的非捕获 lambda的主要内容,如果未能解决你的问题,请参考以下文章
为啥将 lambda 用于非类型模板参数时 gcc 会失败?