ラムダ代入演算子を削除する理由は?

Dec 13 2020

mutableC ++の(偶数)ラムダは、空のキャプチャがない限り割り当てられないことを知りました(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)>);

私はこの選択の背後にある理論的根拠を求めています:なぜoperator=削除されるのですか?特に、デフォルトにできるシナリオ、つまりラムダがmutableあり、すべてのキャプチャがそれ自体でコピー割り当て可能であるシナリオ(l0上記の例など)ではどうでしょうか。

私は承知している。この非のための関連する質問mutableラムダ。しかし、私はそれを回避するのではなく、決定を理解したいと思います。

回答

3 JeffGarrett Dec 14 2020 at 23:59

提案されなかったのではないかと思います。ラムダは、彼らが砂糖である関数オブジェクトよりもはるかに弱く言語に入り、ゆっくりと機能を取り戻しています。特別なメンバー関数に関して、P0624はキャプチャレスラムダに割り当て可能性とデフォルト構成可能性を追加することを提案しました。デフォルトの構成可能性のみがR0で提案されました。これは、作成者が必要とし、間違いなく最も明らかな欠陥であるためですが、割り当て可能性は委員会のフィードバックに基づいてR1で提案されました。

キャプチャを使用したラムダのデフォルトの構成可能性は、確かに言語と一致しています。

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)>);

割り当て可能性も一貫していて便利です。頭に浮かぶほんの一例として、ある時点でstd::default_delete、アロケータの類似物、つまり、std::unique_ptrアロケータに割り当てられたポインタのテンプレートパラメータとして使用できるタイプを用意するという提案がありました。ラムダを使用してアロケーターをキャプチャし、そのような目的に使用することを想像できます。

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??

ただしunique_ptr、ラムダはキャプチャ状態で許可されていても、割り当てを人為的に削除するため、これを再バインド(move-assign to)することはできません。これを関数オブジェクト型として書き直し、関数オブジェクト型unique_ptrに対して生成された代入演算子を使用して代入可能です。

これはほんの一例ですが、キャプチャ状態(std::ref(allocator))に割り当てるかどうかが、呼び出しオペレータがキャプチャ状態に対して実行できることと同じではないことが明確になることを願っています。(リンクされた質問の答えは間違っています。)