【问题标题】:C++20: Non-capturing lambda in non-type template parameterC++20:非类型模板参数中的非捕获 lambda
【发布时间】: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

【问题讨论】:

  • 有什么进展吗?最新的苹果叮当声仍然是同样的抱怨。

标签: c++ templates lambda c++20


【解决方案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 有成员。
  • 这是否意味着它可能具有非公共成员,使其无法传递给模板参数,具体取决于实现?作为上下文,我从stackoverflow.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 采用。
猜你喜欢
  • 2021-02-07
  • 1970-01-01
  • 1970-01-01
  • 2023-02-05
  • 1970-01-01
  • 2014-10-03
  • 2017-02-09
  • 2021-03-18
  • 1970-01-01
相关资源
最近更新 更多