Lambda atama işlecini silmenin arkasındaki mantık?

Dec 13 2020

mutableC ++ 'da bir (çift ) lambda'nın boş yakalamaları (cf ClosureType::operator=) olmadığı sürece atanamayacağını öğrendim .

Misal:

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

Bu seçimin arkasındaki mantığı soruyorum: Neden operator=silinmiş? Özellikle, varsayılan olabileceği senaryolarda, yani lambda mutableve tüm yakalamaların kendileri kopyalanabilir (örneğin l0yukarıdaki örnekte)?

Lambda olmayanlar için bu ilgili sorunun farkındayım mutable. Ancak etrafında çalışmaktansa kararı anlamak isterim.

Yanıtlar

3 JeffGarrett Dec 14 2020 at 23:59

Önerilmediğinden şüpheleniyorum. Lambdalar, dile şeker oldukları işlev nesnelerinden çok daha zayıf girdiler ve yavaş yavaş işlevlerini geri kazanıyorlar. Özel üye işlevleriyle ilgili olarak, P0624 , yakalanamayan lambdalar için atanabilirlik ve varsayılan yapılandırılabilirlik eklemeyi önerdi. R0'da yalnızca varsayılan yapılandırılabilirlik önerildi, çünkü yazarın ihtiyaç duyduğu şey buydu ve tartışmasız en belirgin eksiklikti, ancak R1'de komite geri bildirimlerine dayanarak atanabilirlik önerildi.

Yakalamalı lambdalar için varsayılan yapılandırılabilirlik kesinlikle dil ile tutarlıdır:

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

Görev atanabilirlik de tutarlı ve kullanışlıdır. Akla gelen tek bir örnek olarak, bir noktada std::default_deleteayırıcılar için bir analog , yani std::unique_ptrayırıcı tarafından tahsis edilen işaretçiler için bir şablon parametresi olarak kullanılabilecek bir tür önerisi vardı . Ayırıcıyı yakalamak için bir lambda kullanmayı ve onu böyle bir amaç için kullanmayı hayal edebilirsiniz:

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

Ancak bunu yeniden bağlayamazsınız (taşı-ata) unique_ptrçünkü lambda, yakalama durumu izin verse bile atamayı yapay olarak siler. Bunu bir işlev nesne türü olarak yeniden yazın ve unique_ptratanabilir, işlev nesne türü için oluşturulan atama operatörleri ile.

Bu sadece bir örnek, ancak umarız yakalama durumuna ( std::ref(allocator)) atamak isteyip istemediğinizi , çağrı operatörünün yakalama durumuna yapmasına izin verilenle aynı olmadığını açıklığa kavuşturur . (Bağlantılı sorunun cevabı yanlış.)