删除 lambda 赋值运算符背后的基本原理?

m8m*_*ble 5 c++ lambda

我刚刚了解到 C++ 中的(偶数mutable)lambda 是不可赋值的,除非它有空捕获(cf ClosureType::operator=)。

例子:

auto x = 0;
auto l0 = [copy = x]() mutable {};
auto l1 = []() mutable {};

static_assert(not std::is_copy_assignable_v<decltype(l0)>);
static_assert(std::is_copy_assignable_v<decltype(std::ref(x))>);
static_assert(std::is_copy_assignable_v<decltype(l1)>);
Run Code Online (Sandbox Code Playgroud)

我想问一下这个选择背后的理由:为什么被operator=删除?特别是在可以默认的情况下,即 lambda 是mutable并且所有捕获本身都是可复制分配的(例如l0在上面的示例中)?

我知道非 lambda 的相关问题mutable。但我想理解这个决定,而不是绕过它。

Jef*_*ett 3

我怀疑它没有被提议。Lambda 进入语言时比它们所糖化的函数对象要弱得多,并且慢慢地重新获得功能。关于特殊成员函数,P0624提议为无捕获 lambda 添加可分配性和默认构造性。R0 中仅提出了默认可构造性,因为这是作者所需要的,并且可以说是最明显的缺陷,但可分配性是根据委员会反馈在 R1 中提出的。

带捕获的 lambda 的默认构造性当然与语言一致:

auto x1 = [i = 1]() { return i; };
static_assert(not std::is_default_constructible_v<decltype(x1)>); // why??

struct { int i = 1; auto operator()() { return i; } } x2;
static_assert(std::is_default_constructible_v<decltype(x2)>);
Run Code Online (Sandbox Code Playgroud)

可分配性也是一致且有用的。我想到的一个例子是,在某个时候有人提议std::default_delete为分配器提供一个类似的类型,即一种可以用作std::unique_ptr分配器分配的指针的模板参数的类型。您可以想象使用 lambda 来捕获分配器并将其用于这样的目的:

auto allocator_delete(auto& allocator) {
    using traits = typename std::allocator_traits<std::decay_t<decltype(allocator)>>;
    return [alloc=std::ref(allocator)](typename traits::pointer p) { traits::deallocate(alloc, p, 1); };
}
template<class Alloc> using allocator_deleter_t = decltype(allocator_delete(std::declval<Alloc&>()));
static_assert(not std::is_move_assignable_v<std::unique_ptr<int, allocator_deleter_t<std::allocator<int>>>>);
// why??
Run Code Online (Sandbox Code Playgroud)

但是您无法重新绑定(移动分配) this unique_ptr,因为 lambda 人为地删除了分配,即使其捕获状态允许这样做。将其重写为函数对象类型,并且unique_ptr可以使用为函数对象类型生成的赋值运算符进行赋值。

这只是一个示例,但希望它能够澄清您是否想要分配给捕获状态( )与std::ref(allocator)允许调用运算符对捕获状态执行的操作不同。(链接问题的答案是错误的。)