2011-10-22 22 views
18

Eventuali duplicati:
Recursive lambda functions in c++0xchiamata ricorsiva a lambda (C++ 11)

Perché non posso chiamare un lambda in modo ricorsivo se scrivo come:

auto a = [&] 
{ 
    static int i = 0; i++; 
    std::cout << i << std::endl; 
    if (i<10) 
    a(); //recursive call 
}; 

Fornisce errore di compilazione (ideone):

prog.cpp:8:18: error: '((const main()::<lambda()>*)this)->main()::<lambda()>::a' cannot be used as a function 

prog.cpp: In function 'int main()': 
prog.cpp:9:9: error: variable 'auto a' with 'auto' type used in its own initializer 

Che cosa significa l'errore?

capisco il motivo per cui non riesco a scrivere questo:

auto i=i+1; //error: unable to deduce 'auto' from '<expression error>' 

Non possiamo scrivere questo perché il tipo di i deve essere dedotta dalla sua inizializzazione, il che significa che il tipo non si può dedurre se i appare nell'inizializzazione (ideone). Ma come importa in caso di lambda? Se non sbaglio, il tipo di lambda è determinato dal parametro (i) e dal tipo restituito; non dipende dal corpo se non restituisce nulla (nel qual caso il tipo di ritorno è dedotto come void, indipendentemente dalle altre dichiarazioni nel corpo lambda).

In ogni caso, ho ottenuto una soluzione, e posso usare std::function invece come:

std::function<void()> a = [&] 
{ 
    static int i = 0; i++; 
    std::cout << i << std::endl; 
    if (i<10) 
     a(); 
}; 

che compilare multe (ideone). Ma sono ancora interessato a conoscere il motivo per cui la versione auto non viene compilata.

+0

Penso che il problema sia legato a te che termina con una cattura implicita di 'a', come un riferimento che ricorrerebbe all'infinito perché' a' stesso finisce come membro di 'a'. – Flexo

+0

@awoodland: cosa significa? Per favore, approfondisci. – Nawaz

+3

Il tipo di lambda dipende anche dalle catture, che sospetto sia il problema qui. Se non usi 'a', allora al compilatore non interessa, ma quando lo fai non sa quale tipo di oggetto usare. –

risposta

15

Il motivo è che non vi è alcun caso speciale per gli inizializzatori di espressione lambda delle variabili auto.

Tali casi speciali sarebbero soggetti a errori e abusi. È necessario definire le regole quando si propone che qualcosa come a() dovrebbe funzionare. Come viene visualizzato lo operator()? Qual è lo stato preciso del tipo di a? Il tipo sarà completo? (il che implica che tu già conosci la lista di cattura del lambda). Dopo averlo formulato in un formato ragionevole per una specifica, sarebbe più facile fare dichiarazioni su di esso.

Permettere vostro caso d'uso significherebbe un altro caso in cui è necessario eseguire la scansione avanti nel codice, perché per determinare il tipo di a in a() si deve essere sicuri che l'inizializzazione si conclude con nulla che potrebbe "unlambda" il tipo

struct y { void operator()() { } }; 
template<typename T> y operator+(T, y) { return y(); } 
auto x = [] { x(); } + y(); 

In questo caso, x() chiamerebbe y::operator(), non la lambda.

Come ora, è semplicemente vietato digitare a nell'intero inizializzatore. Perché in C++, auto è non un tipo. È semplicemente un tipo specificatore che sta per un tipo da dedurre. Di conseguenza, un'espressione non può mai avere il tipo auto.

+3

" Se acquisisci un riferimento per riferimento, chiamerai un riferimento che cade quando copi il tuo lambda e l'originale ne esce dall'ambito. " - ovviamente, tutte le acquisizioni per riferimento sono inclini a questo errore/uso improprio, quindi dubito che questo motiva il comitato a non fornire un caso speciale per consentire a "auto a" qui di essere catturato per riferimento. –

+0

Insieme al problema della cattura di 'a', un'altra ragione per cui non è possibile chiamare ricorsivamente la chiusura è che altrimenti non c'è modo di riferirsi a quell'oggetto di chiusura:" questo "designa l'oggetto di classe che lo contiene, se presente. –

6

come la vedo io l'importante differenza tra il auto a caso e il caso std::function<void()> a è che il tipo std::function<void()> non sa/cura su ciò che il tipo di funzione reale si riferisce in realtà è. Scrittura:

std::function<void()> a; 

è perfettamente bene, dove, come:

auto a; 

ha poco senso. Quindi, quando arriva il momento di sintetizzare l'acquisizione se si utilizza std::function<void()>, tutto ciò che deve essere conosciuto sul tipo è già noto, mentre con auto non è ancora noto.

+2

Credo che questa sia la risposta corretta, e che se tu fossi in grado di conoscere il nome del tipo lambda generato dal compilatore (che non puoi), allora potresti usare quel nome nel decl invece di 'auto'. Tuttavia, potrei facilmente vedere questo essere nella categoria "comportamento indefinito". –

+0

@MichaelPrice - Ho provato a scavare la frase su "non si può mai sapere il nome del tipo di lambda" per questa risposta, ma non riesco a ricordare in quale sezione fosse. – Flexo

2

In una funzione ricorsiva f è definita da f e tipo di f è determinato anche dalla f in caso di auto ritorno che porta alla ricorsione infinita.

quando auto tenta di derivare un tipo. decltype (f()) si dedurrà ulteriormente ad un altro decltype (f) `come f deriva da f e.g. anche una chiamata a qualcosa di ricorsivo è ricorsiva. la determinazione del tipo di ritorno diventa ricorsiva se applicata a una funzione ricorsiva. in una funzione ricorsiva la fine della ricorsione può essere eseguita in runtime. ma la determinazione è statica solo

+0

@Neel: Questo non ha senso per me. Quando non c'è una dichiarazione di ritorno nel corpo, come può restituire qualcosa? Stai insinuando che "il livello profondo ricorsivo" significa che il corpo lambda è infinitamente grande da non poter conoscere l'esistenza o la non esistenza delle dichiarazioni di ritorno? – Nawaz

+0

@awoodland: Grazie, non ho ancora esaminato i commenti. @Nawaz: in ogni funzione esiste una possibilità matematica di restituire una scansione sintattica per la sintassi 'return' solo per la visualizzazione. –

+3

+1 per appropriatezza delle immagini –