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

Construction of lambda object in case of specified captures in C++

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

If no captures are specified, the closure type has a defaulted default constructor.

但是捕获的闭包类型又如何构造它们的对象呢?

一种方法是使用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 );
}

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

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

由于它被标记为 language-lawyer,以下是 C++ 标准必须说明的所有内容。

But what about closure types that capture, how can their objects be constructed?

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

The closure type associated with a lambda-expression has no default constructor if the lambda-expression has a lambda-capture and a defaulted default constructor otherwise. It has a defaulted copy constructor and a defaulted move constructor ([class.copy.ctor]). It has a deleted copy assignment operator if the lambda-expression has a lambda-capture and defaulted copy and move assignment operators otherwise ([class.copy.assign]).

但是,子句 1 and 2 表示:

The type of a lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type, called the closure type, whose properties are described below.

The closure type is not an aggregate type. An implementation may define the closure type differently from what is described below provided this does not alter the observable behavior of the program other than by changing: [unrelated stuff]

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

N.B. :lambda 可能 等价于 基于 class 的函子,但它不是 纯粹 语法糖。 compiler/implementation 不需要构造函数来构造和参数化 lambda 的类型。只有 程序员 因缺少构造函数而无法创建实例。

就扩展而言:

But can a compiler provide such constructor as an extension?

是的。允许编译器提供此功能作为扩展,只要它所做的只是使格式错误的程序具有功能性即可。

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

A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any well-formed program. Implementations are required to diagnose programs that use such extensions that are ill-formed according to this document. Having done so, however, they can compile and execute such programs.

但是,对于手头的功能,MSVC 在作为扩展实现时会遇到问题:

  1. 它应该发出诊断信息。
  2. 自己documentation, it should refuse the code when using /permissive-. Yet it does not.

所以看起来 MSVC 有意无意地表现得好像这是语言的一部分,据我所知,情况并非如此。

But what about closure types that capture, how can their objects be constructed?

你不能。它们只能从 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 );
}