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&lt;lambda&gt;),但这可能在 C++20 中通过三向比较发生了变化。对象必须具有外部链接的原因是因为它破坏了模板。 S&lt;+[]&gt;S&lt;+[]&gt; 将被视为不同的类型,即使它们看起来相同(与 S&lt;""&gt; 类似)。

【讨论】:

自 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的主要内容,如果未能解决你的问题,请参考以下文章

C++11 中的非类型可变参数函数模板

C++17和C++11中的非类型模板参数有啥区别?

为啥将 lambda 用于非类型模板参数时 gcc 会失败?

模板类中的非模板函数[关闭]

C++ STL学习 —— 模板泛型算法函数对象lambda 表达式(参数捕获)函数适配器

C++ STL学习 —— 模板泛型算法函数对象lambda 表达式(参数捕获)函数适配器