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

一位同事向我展示了一个 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被调用以获取 object 的值,x否则在main.

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

但是程序是否按照标准格式良好,lambda 对象的这种“构造”是否有效?

回答

但是程序是否按照标准格式良好...

该程序有未定义的行为,条件是给实现者留有余地。特别取决于 lambda 的闭包类型

[this](){ return v; }; 

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

闭包类型在包含相应 lambda 表达式的最小块作用域、类作用域或命名空间作用域中声明。[...] 闭包类型不是聚合类型。一个实现可以定义与下面描述的不同的闭包类型,前提是这不会改变程序的可观察行为,除了改变:

  • (2.1)闭合类型的大小和/或对齐方式,
  • (2.2)闭包类型是否可简单复制([class.prop]),或
  • (2.3) 闭包类型是否为标准布局类([class.prop])。[...]

这意味着是否满足[bit.cast]/1的约束:

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

约束:

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

是实现定义的。

... lambda 对象的这种“构造”是否有效?

由于 [expr.prim.lambda.closure]/2.1 还指出闭包类型的大小和对齐方式是实现定义的,std::bit_cast因此根据[bit .cast]/2 :

返回: 类型的对象To。隐式创建嵌套在结果中的对象 ([intro.object])。结果的值表示的每一位都等于 from 的对象表示中的对应位。结果的填充位未指定。对于结果和其中创建的每个对象,如果没有与所产生的值表示对应的对象类型的值,则行为是未定义的。如果有多个这样的值,则未指定生成哪个值。

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

  • @ user202729 确实,lambas 闭包大小的实现大小更改可能会使(看似)格式良好的程序(如 OP)变得可诊断格式错误,或者更糟的是,未定义。考虑到实现者实现 lambda 的闭包类型的余地,在这些类型的实例上使用诸如 `std::bit_cast` 之类的低级操作似乎本质上是危险的。如果我对上述的解释确实正确,即使在 Lamba 对象上使用 `std::bit_cast` 的(危险)练习可以是格式良好的,也是不合理的。

以上是在C++20中使用`std::bit_cast`创建闭包(lambda)对象是否有效?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>