在 C++ 中指定捕获的情况下构造 lambda 对象

Fed*_*dor 5 c++ lambda visual-studio language-lawyer c++20

从 C++20 开始,不带捕获的闭包类型具有默认构造函数,请参阅https://en.cppreference.com/w/cpp/language/lambda:

如果未指定捕获,则闭包类型具有默认的默认构造函数。

但是对于捕获的闭包类型来说,如何构造它们的对象呢?

一种方法是使用std::bit_cast(前提是闭包类型可以简单地复制)。Visual Studio 编译器为闭包类型提供了一个构造函数,如示例所示:

#include <bit>

int main() {
    int x = 0;
    using A = decltype([x](){ return x; }); 

    // ok everywhere
    constexpr A a = std::bit_cast<A>(1);
    static_assert( a() == 1 );

    // ok in MSVC
    constexpr A b(1);
    static_assert( b() == 1 );
}
Run Code Online (Sandbox Code Playgroud)

演示: https: //gcc.godbolt.org/z/dnPjWdYx1

考虑到 Clang 和 GCC 都拒绝A b(1),标准不要求存在此构造函数。但是编译器可以提供这样的构造函数作为扩展吗?

Nic*_*las 11

但是对于捕获的闭包类型来说,如何构造它们的对象呢?

你不能。它们只能从 lambda 表达式创建。

不,bit_cast并不“在任何地方都有效”。C++ 标准中没有任何规则要求任何特定的 lambda 类型必须是可简单复制的(或者与其捕获成员的大小相同)。当前的实现没有破坏您的代码这一事实并不意味着未来的实现不会。

如果你有多个捕获成员,它肯定行不通。

不要再将 lambda 视为创建类型的廉价方法了。如果您想使用可以构造的成员创建可调用类型,请执行以下操作:

#include <bit>

int main() {
    struct A
    {
      int x = 0;
      constexpr auto operator() {return x;}
    };

    // ok everywhere
    constexpr A b(1);
    static_assert( b() == 1 );
}
Run Code Online (Sandbox Code Playgroud)


小智 8

由于它被标记为language-lawyer,因此 C++ 标准对此有如下规定。

但是对于捕获的闭包类型来说,如何构造它们的对象呢?

cppreference 链接引用的标准的实际部分是[expr.prim.lambda.general] - 7.5.5.1.14:

如果 lambda 表达式具有 lambda 捕获,则与 lambda 表达式关联的闭包类型没有默认构造函数,否则具有默认的默认构造函数。它有一个默认的复制构造函数和一个默认的移动构造函数([class.copy.ctor])。如果 lambda 表达式具有 lambda 捕获,则它具有已删除的复制赋值运算符,否则具有默认的复制和移动赋值运算符 ([class.copy.assign])。

然而,第 1条和第 2条规定:

lambda 表达式的类型(也是闭包对象的类型)是唯一的、未命名的非联合类类型,称为闭包类型,其属性如下所述。

闭包类型不是聚合类型。实现可以定义与下面描述的不同的闭包类型,前提是这不会改变程序的可观察行为,除了更改:[不相关的东西]

这意味着(除了不相关的例外),所描述的 lambda 接口是详尽的。由于除了默认构造函数之外没有列出其他构造函数,因此这是唯一应该存在的构造函数。

注意: lambda 可能相当于基于类的函子,但它并不是纯粹的语法糖。编译器/实现不需要构造函数来构造和参数化 lambda 类型。只是程序员由于缺少构造函数而无法创建实例。

就扩展而言:

但是编译器可以提供这样的构造函数作为扩展吗?

是的。编译器可以提供此功能作为扩展,只要它所做的只是使格式不正确的程序发挥作用。

来自[intro.compliance.general] - 4.1.1.8:

一致的实现可以具有扩展(包括附加的库函数),只要它们不改变任何格式良好的程序的行为。根据本文档,需要实施来诊断使用此类格式不正确的扩展的程序。然而,这样做之后,他们可以编译并执行此类程序。

然而,对于手头的功能,MSVC 在作为扩展的实现中会遇到问题:

  1. 它应该发出诊断信息。
  2. 根据它自己的文档,它在使用时应该拒绝该代码/permissive-。但事实并非如此。

因此,无论是有意还是无意,MSVC 的表现就好像这是该语言的一部分,但据我所知,情况并非如此。