在 C++20 中使用 std::bit_cast 创建闭包(lambda)对象是否有效?

Is it valid to create closure (lambda) objects using `std::bit_cast` in C++20?

一位同事向我展示了一个 C++20 程序,其中闭包对象是使用 std::bit_cast 从它捕获的值虚拟创建的:

#include <bit>
#include <iostream>

class A {
    int v;
public:
    A(int u) : v(u) {}
    auto getter() const { 
        if ( v > 0 ) throw 0;
        return [this](){ return v; }; 
    }
};

int main() {
    A x(42);
    auto xgetter = std::bit_cast<decltype(x.getter())>(&x);
    std::cout << xgetter();
}

此处 main 函数由于异常无法调用 x.getter()。相反,它调用 std::bit_cast 将闭包类型 decltype(x.getter()) 作为模板参数,并将指针 &x 捕获为新的闭包对象 xgetter 作为普通参数。然后调用 xgetter 获取对象 x 的值,否则在 main.

中无法访问该值

该程序被所有编译器接受,没有任何警告并打印 42,演示:https://gcc.godbolt.org/z/a479689Wa

但是程序是否符合标准,这样的 'construction' lambda 对象是否有效?

But is the program well-formed according to the standard ...

该计划为实施者提供了 回旋余地。特别是条件是否为 lambda

的闭包类型
[this](){ return v; }; 

可以轻松复制自;根据 [expr.prim.lambda.closure]/2:

The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression. [...] 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:

  • (2.1) the size and/or alignment of the closure type,
  • (2.2) whether the closure type is trivially copyable ([class.prop]), or
  • (2.3) whether the closure type is a standard-layout class ([class.prop]). [...]

表示是否满足[bit.cast]/1的约束:

template<class To, class From>
constexpr To bit_cast(const From& from) noexcept;

Constraints:

  • (1.1) sizeof(To) == sizeof(From) is true;
  • (1.2) is_­trivially_­copyable_­v<To> is true; and
  • (1.3) is_­trivially_­copyable_­v<From> is true.

是实现定义的。

... and is such 'construction' of lambda objects valid?

由于 [expr.prim.lambda.closure]/2.1 还指出闭包类型的大小和对齐方式是实现定义的,使用 std::bit_cast 创建闭包类型的实例可能会导致程序具有未定义的行为,根据 [bit.cast]/2:

Returns: An object of type To. Implicitly creates objects nested within the result ([intro.object]). Each bit of the value representation of the result is equal to the corresponding bit in the object representation of from. Padding bits of the result are unspecified. For the result and each object created within it, if there is no value of the object's type corresponding to the value representation produced, the behavior is undefined. If there are multiple such values, which value is produced is unspecified.

然而,对于任何类型的实际用途,我认为如果一个构造具有未定义的行为取决于实现余地细节(除非这些可以用说特征查询),那么应该合理地认为该构造具有未定义的行为,编译器的内部 C++(例如 Clang 前端)实现可能除外,其中这些实现细节是已知的。