Il lambda in questione in realtà non ha uno stato .
Esaminare:
struct lambda {
auto operator()() const { return 17; }
};
E se lo avessimo lambda f;, è una classe vuota. Non solo quanto sopra è lambdafunzionalmente simile al tuo lambda, è (fondamentalmente) come viene implementato il tuo lambda! (Richiede anche un cast implicito all'operatore del puntatore di funzione e il nome lambdaverrà sostituito con uno pseudo-guid generato dal compilatore)
In C ++, gli oggetti non sono puntatori. Sono cose reali. Utilizzano solo lo spazio necessario per memorizzare i dati al loro interno. Un puntatore a un oggetto può essere più grande di un oggetto.
Anche se potresti pensare a quel lambda come un puntatore a una funzione, non lo è. Non è possibile riassegnare il auto f = [](){ return 17; };a una funzione o lambda diversa!
auto f = [](){ return 17; };
f = [](){ return -42; };
quanto sopra è illegale . Non c'è spazio fper memorizzare quale funzione verrà chiamata - quell'informazione è memorizzata nel tipo di f, non nel valore di f!
Se l'hai fatto:
int(*f)() = [](){ return 17; };
o questo:
std::function<int()> f = [](){ return 17; };
non stai più immagazzinando direttamente la lambda. In entrambi i casi, f = [](){ return -42; }è legale, quindi in questi casi memorizziamo quale funzione stiamo invocando nel valore di f. E sizeof(f)non è più1 , ma piuttosto sizeof(int(*)())o più grande (fondamentalmente, essere della dimensione di un puntatore o più grande, come ci si aspetterebbe. std::functionHa una dimensione minima implicita nello standard (devono essere in grado di memorizzare "dentro se stessi" chiamabili fino a una certa dimensione) che è grande almeno quanto un puntatore a funzione in pratica).
Nel int(*f)() caso, stai memorizzando un puntatore a una funzione che si comporta come se chiamassi quel lambda. Funziona solo per lambda senza stato (quelli con un []elenco di acquisizione vuoto ).
Nel std::function<int()> f caso, stai creando std::function<int()>un'istanza di classe di cancellazione del tipo che (in questo caso) utilizza il posizionamento new per archiviare una copia del lambda di dimensione 1 in un buffer interno (e, se è stato passato un lambda più grande (con più stato ), userebbe l'allocazione dell'heap).
Probabilmente qualcosa del genere è probabilmente quello che pensi stia succedendo. Che un lambda è un oggetto il cui tipo è descritto dalla sua firma. In C ++, è stato deciso di rendere lambda astrazioni a costo zero rispetto all'implementazione manuale dell'oggetto funzione. Questo ti consente di passare un lambda in un filestd algoritmo (o simile) e di avere il suo contenuto completamente visibile al compilatore quando istanzia il modello di algoritmo. Se un lambda avesse un tipo simile std::function<void(int)>, il suo contenuto non sarebbe completamente visibile e un oggetto funzione creato a mano potrebbe essere più veloce.
L'obiettivo della standardizzazione C ++ è la programmazione di alto livello con zero overhead rispetto al codice C realizzato a mano.
Ora che hai capito che il tuo fè di fatto apolide, dovrebbe esserci un'altra domanda nella tua testa: la lambda non ha stato. Perché non ha le dimensioni 0?
C'è la risposta breve.
Tutti gli oggetti in C ++ devono avere una dimensione minima di 1 in base allo standard e due oggetti dello stesso tipo non possono avere lo stesso indirizzo. Questi sono collegati, perché un array di tipo Tavrà gli elementi sizeof(T)separati.
Ora, poiché non ha uno stato, a volte non può occupare spazio. Questo non può accadere quando è "da solo", ma in alcuni contesti può accadere. std::tuplee un codice di libreria simile sfrutta questo fatto. Ecco come funziona:
Poiché un lambda è equivalente a una classe con operator()sovraccarico, lambda senza stato (con un []elenco di acquisizione) sono tutte classi vuote. Hanno sizeofdi 1. In effetti, se erediti da loro (cosa consentita!), Non occuperanno spazio fintanto che non causano una collisione di indirizzi dello stesso tipo . (Questo è noto come l'ottimizzazione della base vuota).
template<class T>
struct toy:T {
toy(toy const&)=default;
toy(toy &&)=default;
toy(T const&t):T(t) {}
toy(T &&t):T(std::move(t)) {}
int state = 0;
};
template<class Lambda>
toy<Lambda> make_toy( Lambda const& l ) { return {l}; }
lo sizeof(make_toy( []{std::cout << "hello world!\n"; } ))è sizeof(int)(beh, quanto sopra è illegale perché non puoi creare un lambda in un contesto non valutato: devi creare un nome e auto toy = make_toy(blah);poi farlo sizeof(blah), ma questo è solo rumore). sizeof([]{std::cout << "hello world!\n"; })è ancora 1(qualifiche simili).
Se creiamo un altro tipo di giocattolo:
template<class T>
struct toy2:T {
toy2(toy2 const&)=default;
toy2(T const&t):T(t), t2(t) {}
T t2;
};
template<class Lambda>
toy2<Lambda> make_toy2( Lambda const& l ) { return {l}; }
questo ha due copie del lambda. Poiché non possono condividere lo stesso indirizzo, sizeof(toy2(some_lambda))è 2!
structcon anoperator())