无捕获的 lambda 是结构类型吗?

dfr*_*fri 7 c++ lambda templates language-lawyer c++20

C++20 接受的P1907R1引入了结构类型,这是非类型模板参数的有效类型。

GCC 和 Clang 都接受以下 C++2a 代码片段:

template<auto v>
constexpr auto identity_v = v;

constexpr auto l1 = [](){};
constexpr auto l2 = identity_v<l1>; 
Run Code Online (Sandbox Code Playgroud)

暗示无捕获 lambda 的类型是结构类型。

  • 无捕获 lambda 确实满足其类型成为结构类型的要求吗?

dfr*_*fri 7

除非另有明确说明,以下所有标准参考均指N4861(2020 年 3 月后布拉格工作草案/C++20 DIS)


无捕获 lambda 的类型(其闭包类型)是结构类型

以后,我们将把 lambda 的类型单独称为闭包类型

如下面的标准段落所示,无捕获 lambda 的闭包类型:

  • 满足它是文字(类)类型的要求,而且
  • 满足对文字类型的要求,使其成为结构类型

并且因此可以用作非类型模板参数的类型,例如示例片段

template<auto v>
constexpr auto identity_v = v;

constexpr auto l1 = [](){};
constexpr auto l2 = identity_v<l1>;
Run Code Online (Sandbox Code Playgroud)

确实是良构的。


lambda 的闭包类型是非联合类类型

[expr.prim.lambda.closure]/1 [强调我的]管辖

类型一的λ-表达(这也是封闭的对象的类型)是一个独特的,无名不愈合类型,被称为闭合类型,其属性描述如下。

闭包类型是非联合类类型


无捕获 lambda 的闭包类型是文字(类)类型

[basic.types]/10 [extract,强调我的]管辖

一个类型是文字类型,如果它是:

  • [...]
  • 一个可能有 cv 限定的类类型,它具有以下所有属性:
    • 它有一个 constexpr 析构函数([dcl.constexpr]),
    • 要么是一个闭包类型([expr.prim.lambda.closure]),一个聚合类型([dcl.init.aggr]),要么至少有一个 constexpr 构造函数或构造函数模板(可能从基类继承)不是复制或移动构造函数,
    • 如果是联合,则其非静态数据成员中至少有一个是非易失性文字类型,并且
    • 如果它不是联合,则其所有非静态数据成员和基类都是非易失性文字类型。

闭包类型是文字类型,如果

  • 有一个 constexpr 析构函数,如果
  • 它的所有非静态数据成员都是非易失性文字类型。

无捕获 lambda 的闭包类型没有非静态数据成员,因此满足后一个要求。前者呢,一个 constexpr 析构函数?

隐式生成的 constexpr 析构函数

[expr.prim.lambda.closure]/14管辖

lambda 表达式关联的闭包类型具有隐式声明的析构函数 ([class.dtor])。

闭包类型的析构函数是隐式声明的。此外,[/dcl.fct.def.default ]/ 5描述了[提取,强调我的]

显式默认函数和隐式声明函数统称为默认函数,实现应为其提供隐式定义([class.ctor], [class.dtor] , [class.copy.ctor], [class.copy.分配]), [...]

集体术语默认函数还包括隐式声明的析构函数。

最后,[class.dtor]/9

如果默认析构函数满足 constexpr 析构函数 ([dcl.constexpr]) 的要求,那么它就是 constexpr 析构函数。

描述默认的析构函数是 constexpr 析构函数,如果它们满足[dcl.constexpr]的要求,特别是[dcl.constexpr]/3[dcl.constexpr]/5 [摘录,强调我的]

[dcl.constexpr]/3 constexpr 函数的定义应满足以下要求:

  • [...]
  • 如果函数是构造函数或析构函数,则其类不应有任何虚拟基类;
  • [...]

[dcl.constexpr]/5函数体不是的constexpr析构函数的定义= delete还需要满足以下要求:

  • 对于类类型或其(可能是多维)数组的每个子对象,该类类型应具有 constexpr 析构函数。

所有这些都满足无捕获 lambda 的闭包类型(没有基类,也没有子对象;后者见[intro.object]/2)。

因此,无捕获 lambda 的闭包类型是文字类型。


无捕获 lambda 的闭包类型是结构类型

根据[temp.param]/6[temp.param]/7 [提取,强调我的]

[temp.param]/6非类型模板参数应具有以下(可能是 cv 限定的)类型之一:

  • 结构类型(见下文),
  • [...]

[温度参数]/7

结构类型是下列之一:

  • 标量类型,或
  • 左值引用类型,或
  • 具有以下属性文字类类型
    • 所有基类和非静态数据成员都是公共的和非可变的,并且
    • 所有基类和非静态数据成员的类型都是结构类型或(可能是多维的)数组。

如果文字类类型没有基类和非静态数据成员,那么它就是结构类型。这两者都适用于无捕获 lambda,因此,无捕获 lambda 的闭包类型是结构类型。


关于允许 lambda 的闭包类型为文字类型的初衷的一些说明

N4487提议允许某些 lambda 表达式和对某些闭包对象的操作出现在常量表达式中,并包含一个专门的部分,用于讨论闭包类型是文字类型的主题:

如果其每个数据成员的类型都是文字类型,则闭包对象应该是文字类型。

C++14 中的闭包类型永远不能是文字类型——即使它的所有数据成员都是文字类型——因为它缺少一个不是复制或移动构造函数的 constexpr 构造函数。如果允许这样的闭包类型具有隐式定义的默认构造函数,它将是 constexpr,使其成为文字类型。但是,因为根据定义,闭包类型必须删除其默认构造函数,所以禁止实现隐式定义一个。[...]

P0170R1包含来自 N4487 的核心措辞,已被 C++17 接受并实现。

然而此时(C++14和C++17),析构函数不能是constexpr,因此自然不要求字面类型有constexpr析构函数;[basic.types] /10.5.1在N4140(C ++ 14),以及[basic.types] /10.5.1在N4659(C ++ 17),而不是所要求的析构函数是微不足道:

一个类型是文字类型,如果它是:

  • [...]
  • 具有以下所有属性的类类型(Clause [class]):
    • 它有一个微不足道的析构函数,
    • [...]

P1907R1被 C++20 接受,扩展了模板参数对象具有持续销毁的要求;[temp.param]/8 [强调我的]:

命名为类 type的非类型模板参数id 表达式表示类型为 的静态存储持续时间对象,称为模板参数对象,其值是相应模板参数转换为类型后的值模板参数。同类型程序中的所有这些模板参数具有相同的值表示相同的模板参数对象。[...]模板参数对象应不断销毁。Tconst T

并且,P0784R7也被 C++20 接受,特别包含了 constexpr 析构的引入,包括对类型为文字类型的要求的更新;在论文的早期版本P0784R1 中特别描述:

constexpr 析构函数的建议规则是:

  • [...]
  • 文字类型需要 constexpr 析构函数(以前,对平凡析构函数提出了更强的要求)


303*_*303 3

随着缺陷报告CWG2542的解决,lambdas\xe2\x80\x94 无论是否是无捕获的\xe2\x80\x94 现在都明确定义为属于结构类型,因此不能用作 NTTP。这意味着编译器可以实现CWG2542,并拒绝尝试使用任何类型的 lambda 作为 NTTP 的 C++20(或更新版本)代码。

\n

7.5.5.2 ( [expr.prim.lambda.closure] ) 第 3 段的相关段落现在如下:

\n
\n

闭包类型不是聚合类型 ( [dcl.init.aggr] ),也不是\n结构类型 ( [temp.param] )。

\n
\n