【问题标题】:Are captureless lambdas structural types?无捕获 lambda 是结构类型吗?
【发布时间】:2021-02-04 05:28:43
【问题描述】:

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

GCC 和 Clang 都接受以下用于 C++2a 的 sn-p:

template<auto v>
constexpr auto identity_v = v;

constexpr auto l1 = [](){};
constexpr auto l2 = identity_v<l1>; 

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

问题

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

【问题讨论】:

  • 和以前的一些情况一样,我的诚实问题最终得到了我自己的回答,在详细写出来的过程中。
  • @AmirKirsh 谢谢,我看到我实际上评论了那个特定的答案。对我来说缺少的关键(当我写这个问题时)是无捕获 lambda 的析构函数是否为 constexpr,这是其闭包类型为文字类型的要求(链接中未涵盖的要求回答)。我通过(间接?)找到了这个保证,即 A) 一个 defaulted destructor 是 constexpr 如果可以的话,并且 B) 是隐式定义的析构函数属于默认析构函数的总称。

标签: c++ templates lambda language-lawyer c++20


【解决方案1】:

以下所有标准参考资料,除非另有明确说明,均指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 析构函数(以前,对普通析构函数提出了更强的要求)

【讨论】:

  • 如何确定无捕获 lambda 没有非静态数据成员? (不是说它应该…)
  • @David Herring:我同意不应该,但这是个好问题; [expr.prim.lambda.closure]/2 是我发现的最接近的:“一个实现可以定义不同于下面描述的闭包类型,前提是这个不会改变程序的可观察行为,而不是通过改变 : ...",但待定它感觉比我希望的要弱(考虑到与 constexpr/literal 类型 lambda 相关的已接受提案的意图)。有什么想法吗?
  • 我认为它没有被指定(即使没有明确的实现范围)。我们也不知道捕获成员的访问权限(否则这无关紧要,因为它们没有名称),因此即使具有某些 init-capture 的 lambdas 也可能是结构性的!如果此用例非常重要,则可能需要提出建议。
  • @DavisHerring 我明白了,谢谢。虽然这可能是规范文本中的一个空白,但相关的、已接受的提案的意图(例如 N4487:"...如果 其所有数据成员的初始化(对应于 到每个捕获)") 和其他相关的规范段(expr.prim.lambda.capture/12: "[...] 附加未命名的非静态数据成员 [...] 如果声明,这样的非静态数据成员应该是文字类型。") 可以说似乎暗示着额外的非静态数据成员不应该是......
  • ... 允许影响闭包类型是否为文字(以及从 C++20 开始:结构)类型。
猜你喜欢
  • 1970-01-01
  • 2019-05-06
  • 1970-01-01
  • 2014-02-18
  • 2020-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-16
相关资源
最近更新 更多