C++20:非捕获lambda表达式作为非类型模板参数

10

C++20是否允许将非捕获lambda衰减为函数指针并直接作为非类型模板参数传递?如果是,正确的语法是什么?

我已尝试在各种版本的clang和gcc中使用-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编译代码没有任何投诉,代码按预期运行。

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


有进展吗?最新的苹果clang还是同样的抱怨。 - Joey.Z
3个回答

11
C++20允许将非捕获的lambda衰减为函数指针直接传递给非类型模板参数吗?
是的。
实际上,你甚至可以更进一步 - 你甚至不需要将lambda转换为函数指针。你可以直接提供lambda。这在C++20中是有效的。
using Y = S<[](int x) -> void { std::cout << x << " hello\n"; }>;

抱歉,根据CWG 2542,这不再被视为有效的C++20代码,因为lambda表达式不是“结构化”类型(指可用作非类型模板参数类型的类型)。
在C++20中,我们有一个规则,即允许在未求值的上下文中使用lambda表达式(P0315)。在那里,除了许多其他措辞变化之外,这篇论文还撤销了阻止lambda表达式在模板参数(C++17的[expr.prim.lambda]/2)中使用的规则:

一个lambda表达式不得出现在未求值的操作数、模板参数、别名声明、typedef声明或函数或函数模板的声明中,除非在其函数体和默认参数之外。

在C++20中,这个条款不再存在。

解除这个限制允许将lambda用作模板参数,并且在C++17中,从无捕获lambda到函数指针的转换已经是constexpr的。clang目前还没有实现这个功能(在gcc上编译using T = decltype([]{});可以通过,但在clang上还不行)。我不会立即将这称为clang的错误,只是一个尚未实现的功能(在未评估的上下文中使用lambda还没有被列为已实现的功能,可以参考cppreference编译器支持页面)。
C++20的非类型模板参数(P1907)甚至允许省略+,因为无捕获的lambda函数被视为结构类型[temp.param]/7),因为它们根本没有任何数据成员。

标准在哪里说捕获列表为空的lambda没有任何数据成员? - Danra
@Danra 标准并没有规定Lambda是否具有成员。 - Barry
因为clang还没有实现这个功能。 - Barry
我想知道为什么clang还没有它。甚至没有计划。即使MSVC已经支持它。 - facetus
1
这个答案已经过时了。根据CWG 2542的解决方案,无捕获的lambda表达式不能是结构类型。(如上所述,之前并没有明确指定。) - undefined
显示剩余4条评论

0
如果自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] (https://timsong-cpp.github.io/cppwp/n3337/temp.arg.nontype#1.3),因此尽管我自己也很难找到C++17禁止这种情况的确切位置,但我不认为链接是这里的解释... - Michael Kenzel
另外,请注意字符串字面值是否引用不同的对象是未指定的[lex.string]/16。而链接是名称的属性[basic.link]。由于字符串字面值没有名称,因此它们不能具有链接... - Michael Kenzel

-2

模板参数必须是一个constexpr变量。

有一个与lambda相关的提案N4487

我不知道它是否被纳入了C++20。


1
这是为C++17采用的。 - Barry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接