2013-04-15 12 views
11

Racchiudere parametri di un modello variadic nelle liste di inizializzazione dovrebbero assicurare che stanno valutati in ordine, ma non sta accadendo qui:C++ 11 liste di inizializzazione sui parametri del modello variadic: Perché non è questo lavoro

#include <iostream> 
using namespace std; 


template<class T> void some_function(T var) 
{ 
    cout << var << endl; 
} 

struct expand_aux { 
    template<typename... Args> expand_aux(Args&&...) { } 
}; 

template<typename... Args> inline void expand(Args&&... args) 
{ 
    bool b[] = {(some_function(std::forward<Args>(args)),true)...}; // This output is 42, "true", false and is correct 
    cout << "other output" << endl; 
    expand_aux temp3 { (some_function(std::forward<Args>(args)),true)... }; // This output isn't correct, it is false, "true", 42 
} 

int main() 
{ 
    expand(42, "true", false); 

    return 0; 
} 

Come mai?

+2

Errore del compilatore? Dal tuo link fornito, passando da gcc a clang o Intel produrrai i risultati che ti aspetti. –

+1

Hai ragione Drew, Clang lo sta facendo bene – Paul

+0

(o comportamento non definito ...) –

risposta

12

Questo sembra essere un errore . L'output dovrebbe essere quello che ti aspetti.

Mentre non vi è alcuna garanzia sull'ordine di valutazione degli argomenti di una chiamata costruttore in generale, esiste una garanzia sull'ordine di valutazione delle espressioni in una lista di inizializzazione rinforzata.

Per Paragrafo 8.5.4/4 del C++ 11 standard:

All'interno di inizializzazione-list di a-init-list rinforzato, l'inizializzatore-clausole, comprese quelle che derivano dalla confezione Le espansioni (14.5.3), sono valutate nell'ordine in cui appaiono. Vale a dire, ogni computo del valore e l'effetto collaterale di associato a una data clausola di inizializzazione viene sequenziato prima di ogni calcolo del valore e dell'effetto laterale associato a qualsiasi clausola di inizializzatore che la segue nell'elenco separato da virgole dell'elenco di inizializzatore. [Nota: questo ordine di valutazione è valido indipendentemente dalla semantica dell'inizializzazione; ad esempio, si applica quando gli elementi dell'elenco inizializzatore vengono interpretati come argomenti di una chiamata del costruttore, anche se ordinariamente non ci sono vincoli di sequenziamento sugli argomenti di una chiamata. -end note]

+3

sembra che gcc abbia appena applicato il normale ordine di valutazione degli argomenti di chiamata alle chiamate di inizializzazione uniformi che sono normali chiamate del costruttore. Ma tbh, è anche un po 'confuso che 'Foo x {a, b};' e 'Foo x (a, b);' non siano gli stessi anche se viene chiamato lo stesso costruttore. –

+0

@ArneMertz: In effetti, l'inizializzazione uniforme è * molto * meno uniforme di quanto pretenda di essere. –

+0

Non lo direi. Avere elenchi di inizializzatori aggregati valutati da sinistra a destra e non aggregati al contrario non è chiaramente molto uniforme, indipendentemente dal fatto che siano stati scritti con parentesi graffe/parentesi in C++ 03. Imo è un bug (ora non riparabile) o almeno un'incoerenza nello standard che ha permesso ai compilatori di valutare la funzione e in particolare gli argomenti del costruttore da valutare da destra a sinistra come fa gcc. Definire l'ordine di valutazione è la cosa giusta per garantire la portabilità tra diversi compilatori e sarebbe stato per la valutazione degli argomenti di funzione nei vecchi tempi. –

4

Come notato, il problema è un errore del compilatore. Il tuo codice, come scritto, dovrebbe valutare i suoi argomenti in ordine.

Il mio consiglio sarebbe quello di essere esplicito su cosa si vuole fare, e in quale ordine lo si sta facendo ed evitare di abusare dell'operatore virgola (a parte, il codice potrebbe comportarsi in modo strano se some_function ha restituito un tipo che sovrascrive operator,) o usando le liste di inizializzazione garanzie (che, anche se standard, sono anche relativamente oscure).

Il mio andare soluzione è quella di scrivere e quindi utilizzare do_in_order:

// do nothing in order means do nothing: 
void do_in_order() {} 
// do the first passed in nullary object, then the rest, in order: 
template<typename F0, typename... Fs> 
void do_in_order(F0&& f0, Fs&&... fs) { 
    std::forward<F0>(f0)(); 
    do_in_order(std::forward<Fs>(fs)...); 
} 

che si utilizza in questo modo:

do_in_order([&]{ some_function(std::forward<Args>(args)); }...); 

si avvolge l'azione che si vuole fare in una full nullaria anonimo catturare lambda, quindi utilizzare ... per creare un intero insieme di istanze di detti lambda e passare a do_in_order, che li chiama in ordine tramite inoltro perfetto.

Questo dovrebbe essere facile per un compilatore in linea e ridurre a una sequenza di chiamate. E dice quello che fa direttamente, e non richiede strani cast di void, l'uso dell'operatore virgola o array il cui valore viene scartato.

Problemi correlati