在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();
}
由于出现异常,HEREmain
函数无法调用x.getter()
。相反,它调用std::bit_cast
,将闭包类型decltype(x.getter())
作为模板参数,并将为新闭包对象xgetter
捕获的指针&x
作为普通参数。然后调用xgetter
获取对象x
的值,否则在main
中无法访问。
程序被所有编译器接受,没有任何警告并打印42
,demo:https://gcc.godbolt.org/z/a479689Wa
但是程序是否按照标准格式良好,这种lambda对象的"构造"是否有效?
解决方案
但该程序是否符合标准...
该程序为实施者提供了undefined behaviour conditional on回旋余地。特别是根据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对象的"构造"是否有效?
as[expr.prim.lambda.闭包]/2.1还声明闭包类型的大小和对齐是由实现定义的,使用std::bit_cast
创建闭包类型的实例可能会导致程序行为未定义,根据[bit.cast]/2:
返回:
To
类型的对象。隐式创建嵌套在结果中的对象([Intro.Object])。结果的值表示的每一位等于From的对象表示中的对应位。未指定结果的填充位。对于结果和在其中创建的每个对象,如果没有与生成的值表示相对应的对象类型的值,则行为未定义。如果有多个这样的值,则未指定生成的值。
然而,对于任何类型的实际使用,我认为,如果一个构造有未定义的行为,条件是实现的回旋余地细节(除非这些可以用特征来查询),那么该构造应该合理地被认为具有未定义的行为,可能除了编译器的内部C++(例如Clang前端)实现,在那里这些实现细节是已知的。
相关文章