Gründe für das Löschen des Lambda-Zuweisungsoperators?

Dec 13 2020

Ich habe gerade erfahren, dass ein (gerades mutable) Lambda in C ++ nur zuweisbar ist, wenn es leere Captures enthält (vgl. ClosureType::operator=).

Beispiel:

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

Ich frage nach der Begründung für diese Wahl: Warum wird die operator=gelöscht? Insbesondere in Szenarien, in denen die Standardeinstellung verwendet werden könnte, dh das Lambda ist mutableund alle Erfassungen selbst kopierbar sind (z. B. l0im obigen Beispiel)?

Mir ist diese verwandte Frage für Nicht- mutableLambdas bekannt. Aber ich würde die Entscheidung lieber verstehen, als sie zu umgehen.

Antworten

3 JeffGarrett Dec 14 2020 at 23:59

Ich vermute, es wurde nicht vorgeschlagen. Lambdas betraten die Sprache viel schwächer als die Funktionsobjekte, für die sie Zucker sind, und gewinnen langsam wieder an Funktionalität. In Bezug auf die speziellen Elementfunktionen schlug P0624 vor , die Zuweisbarkeit und Standardkonstruierbarkeit für Captureless Lambdas hinzuzufügen. In R0 wurde nur die Standardkonstruierbarkeit vorgeschlagen, da dies vom Autor benötigt wurde und wohl der offensichtlichste Mangel ist. In R1 wurde jedoch die Zuweisbarkeit auf der Grundlage des Feedbacks des Ausschusses vorgeschlagen.

Die Standardkonstruierbarkeit für Lambdas mit Captures stimmt sicherlich mit der Sprache überein:

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

Die Zuweisbarkeit ist ebenfalls konsistent und nützlich. Als nur ein Beispiel, das mir in den Sinn kommt, gab es irgendwann einen Vorschlag, ein Analogon std::default_deletefür Allokatoren zu haben, dh einen Typ, der als Vorlagenparameter std::unique_ptrfür Allokator-zugewiesene Zeiger verwendet werden könnte. Sie können sich vorstellen, ein Lambda zu verwenden, um den Allokator zu erfassen und für einen solchen Zweck zu verwenden:

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

Sie können dies jedoch nicht erneut binden (verschieben oder zuweisen) unique_ptr, da das Lambda die Zuweisung künstlich löscht, obwohl der Erfassungsstatus dies zulässt. Schreiben Sie dies als Funktionsobjekttyp um und das unique_ptrist mit generierten Zuweisungsoperatoren für den Funktionsobjekttyp zuweisbar.

Dies ist nur ein Beispiel, aber es wird hoffentlich klargestellt, dass die Frage, ob Sie dem Erfassungsstatus (the std::ref(allocator)) zuweisen möchten oder nicht, nicht mit dem übereinstimmt, was der Anrufbetreiber für den Erfassungsstatus tun darf. (Die Antwort der verknüpften Frage ist falsch.)