以下所有标准参考资料,除非另有明确说明,均指N4861 (March 2020 post-Prague working draft/C++20 DIS)。
无捕获 lambda 的类型(其闭包类型)是结构类型
以后,我们将 lambda 的类型单独称为 闭包类型。
如下标准段落所示,无捕获 lambda 的闭包类型:
- 满足文字(类)类型的要求,而且
- 满足文字类型的要求,使其成为结构类型,
因此可以用作非类型模板参数的类型,例如示例 sn-p
template<auto v>
constexpr auto identity_v = v;
constexpr auto l1 = [](){};
constexpr auto l2 = identity_v<l1>;
确实格式正确。
lambda 的闭包类型是非联合类类型
由[expr.prim.lambda.closure]/1 [强调 我的]
lambda-expression的类型(也是闭包对象的类型)是唯一的、未命名的非联合类类型,称为闭包类型,其属性如下所述。
闭包类型是一个非联合类类型。
无捕获 lambda 的闭包类型是文字(类)类型
由[basic.types]/10 [摘录,强调我的]
一个类型是一个文字类型如果它是:
- [...]
- 可能是 cv 限定的类类型,具有以下所有属性:
-
它有一个 constexpr 析构函数 ([dcl.constexpr]),
-
它是闭包类型([expr.prim.lambda.closure]),聚合类型([dcl.init.aggr]),或者至少有一个常量表达式
构造函数或构造函数模板(可能从基类继承
class) 不是复制或移动构造函数,
- 如果是联合,则至少有一个非静态数据成员是非易失性文字类型,并且
- 如果不是联合,则其所有非静态数据成员和基类都是非易失文字类型。
闭包类型是文字类型如果
- 有一个 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.assign]), [...]
defaulted 函数的统称还包括隐式声明的析构函数。
最后,[class.dtor]/9
如果满足 constexpr 析构函数 ([dcl.constexpr]) 的要求,默认析构函数就是 constexpr 析构函数。
如果默认析构函数满足[dcl.constexpr] 的要求,特别是[dcl.constexpr]/3 和[dcl.constexpr]/5 [摘录,强调我的]
,请描述默认析构函数是 constexpr 析构函数
[dcl.constexpr]/3 constexpr 函数的定义应满足以下要求:
- [...]
-
如果函数是构造函数或析构函数,其类不应有任何虚拟基类;
- [...]
[dcl.constexpr]/5function-body不是= delete的constexpr析构函数的定义还应满足以下要求:
- 对于每个类类型的子对象或其(可能是多维的)数组,该类类型应具有一个 constexpr 析构函数。
所有这些都适用于无捕获 lambda 的闭包类型(没有基类,也没有子对象;有关后者,请参见 [intro.object]/2)。
因此,无捕获 lambda 的闭包类型是文字类型。
无捕获 lambda 的闭包类型是结构类型
根据[temp.param]/6 和[temp.param]/7 [摘录,强调我的]
[temp.param]/6 非类型模板参数应具有以下类型之一(可能是 cv 限定的):
[temp.param]/7
结构类型是以下之一:
- 标量类型,或
- 左值引用类型,或
- 具有以下属性的文字类类型:
- 所有基类和非静态数据成员都是公共的、不可变的,并且
- 所有基类和非静态数据成员的类型都是结构类型或其(可能是多维的)数组。
如果字面量类类型没有基类和非静态数据成员,那么它通常是结构类型。这两种情况都适用于无捕获 lambda,因此无捕获 lambda 的闭包类型是一种结构类型。
关于允许 lambda 的闭包类型为文字类型的原始意图的一些注释
N4487 建议允许某些 lambda 表达式和对某些闭包对象的操作出现在常量表达式中,并包含一个专门的部分来讨论闭包类型是文字类型的主题:
如果闭包对象的每个数据成员的类型都是文字类型,则闭包对象应该是文字类型。
C++14 中的闭包类型永远不可能是文字类型——即使它的所有
数据成员是文字类型——因为它缺少 constexpr
不是复制或移动构造函数的构造函数。如果这样的关闭
类型被允许有一个隐式定义的默认构造函数
将是 constexpr,使其成为文字类型。但是,因为关闭
根据定义,类型必须删除其默认构造函数,
禁止该实现隐式定义一个。 [...]
P0170R1,包含来自 N4487 的核心措辞,已被 C++17 接受并实现。
此时(C++14 和 C++17),然而,析构函数不能是 constexpr,因此自然不需要文字类型具有 constexpr 析构函数; N4140 (C++14) 中的 [basic.types]/10.5.1 以及 N4659 (C++17) 中的 [basic.types]/10.5.1 要求析构函数是微不足道的:
一个类型是文字类型,如果它是:
- [...]
- 具有以下所有属性的类类型(子句 [class]):
P1907R1,C++20 接受,扩展了对模板参数对象的要求,使其具有常量销毁; [temp.param]/8 [强调我的]:
id-expression 命名类类型为T 的非类型模板参数 表示const T 类型的静态存储持续时间对象,称为模板参数对象,其值为对应模板参数转换为模板参数类型后的值。相同类型的程序中所有具有相同值的模板参数表示相同的模板参数对象。 [...] 模板参数对象应具有不断的破坏。
并且,P0784R7,也被 C++20 接受,特别包含 constexpr 破坏的引入,包括对类型为文字类型的要求的更新;特别是在早期版本的论文中进行了描述,P0784R1:
constexpr 析构函数的建议规则是:
- [...]
- 文字类型需要 constexpr 析构函数(以前,对普通析构函数提出了更强的要求)