2011-10-08 11 views
9
#include <initializer_list> 
#include <utility> 

void foo(std::initializer_list<std::pair<int,int>>) {} 
template <class T> void bar(T) {} 

int main() { 
    foo({{0,1}}); //This works 
    foo({{0,1},{1,2}}); //This works 
    bar({{0,1}}); //This warns 
    bar({{0,1},{1,2}}); //This fails 
    bar(std::initializer_list<std::pair<int,int>>({{0,1},{1,2}})); //This works 
} 

Questo non viene compilato in gcc 4.5.3, dà un avvertimento per la linea tracciata dicendo deducing ‘T’ as ‘std::initializer_list<std::initializer_list<int> >’ e un errore per la linea tracciata dicendo no matching function for call to ‘bar(<brace-enclosed initializer list>)’. Perché gcc può dedurre il tipo della prima chiamata alla battuta ma non la seconda, e c'è un modo per aggiustarlo a parte il casting lungo e brutto?modelli non sempre indovinate inizializzazione tipi di elenco

+0

possibile duplicato di [Perché il modello non accetta un elenco di inizializzazione] (http://stackoverflow.com/questions/4757614/why-doesnt-my-template-accept-an-initializer-list) - Quella domanda è fondamentalmente un duplicato. Il fatto che la deduzione dei tipi venga effettuata su una lista di inizializzatori è un'estensione g ++. – Omnifarious

+0

@Sempre non sono sicuro se si tratta di questi. la mia domanda era intesa per chiedere meno dettagli di quella apparentemente questa domanda. –

risposta

18

GCC secondo C++ 11 non può dedurre il tipo per le prime due chiamate su bar. Avvisa perché implementa un'estensione da a C++ 11.

La norma dice che quando un argomento di funzione in una chiamata a un modello di funzione è un { ... } e il parametro non è initializer_list<X> (opzionalmente un parametro di riferimento), che poi il tipo di parametro non può essere dedotta dalla {...}. Se il parametro è tale initializer_list<X>, gli elementi dell'elenco di inizializzazione vengono dedotti in modo indipendente confrontando lo X e ciascuna delle deduzioni degli elementi deve corrispondere.

template<typename T> 
void f(initializer_list<T>); 

int main() { 
    f({1, 2}); // OK 
    f({1, {2}}); // OK 
    f({{1}, {2}}); // NOT OK 
    f({1, 2.0}); // NOT OK 
} 

In questo esempio, il primo è OK, e il secondo è OK anche perché i primi rendimenti Elemento di int, e il secondo elemento confronta {2} contro T - questa deduzione non può cedere un constradiction in quanto non è così dedurre qualsiasi cosa, quindi alla fine la seconda chiamata prende T come int. Il terzo non può dedurre T da alcun elemento, quindi NON è OK. L'ultima chiamata produce detrazioni contraddittorie per due elementi.

Un modo per fare questo lavoro è quello di utilizzare un tale tipo come tipo di parametro

template <class T> void bar(std::initializer_list<std::initializer_list<T>> x) { 
    // ... 
} 

Vorrei sottolineare che facendo std::initializer_list<U>({...}) è pericoloso - meglio rimuovere quelli (...) intorno le parentesi graffe. Nel tuo caso capita di lavorare per caso, ma prendere in considerazione

std::initializer_list<int> v({1, 2, 3}); 
// oops, now 'v' contains dangling pointers - the backing data array is dead! 

La ragione è che ({1, 2, 3}) chiama il costruttore di copia/spostamento dei initializer_list<int> passandogli un temporaneo initializer_list<int> associato alla {1, 2, 3}. L'oggetto temporaneo verrà quindi distrutto e muoiono al termine dell'inizializzazione. Quando l'oggetto temporaneo associato alla lista muore, anche l'array di backup contenente i dati verrà distrutto (se la mossa viene eliminata, vivrà fino a "v", che è male, poiché non si comporterebbe nemmeno male garantito!). Omettendo il paren, v è direttamente associato all'elenco e i dati dell'array di supporto vengono distrutti solo quando viene distrutto v.

+0

Non ho capito l'ultimo paragrafo. Potresti approfondire un po 'di più? Non vedo alcuna differenza tra 'std :: initializer_list v ({1, 2, 3});' e 'std :: initializer_list v {1, 2, 3};'. Mi sembrano uguali. – Nawaz

+0

@Nawaz pensavo di averlo elaborato il più possibile. Se hai ancora domande, ti consiglio di aprire una nuova domanda, perché l'elaborazione necessaria garantirà una nuova risposta. –

+0

@Nawaz hai aperto una nuova domanda per l'ultimo paragrafo? Potresti inoltrarlo qui nei commenti. – SebNag

4

L'inizializzazione uniforme si basa sulla conoscenza del tipo di inizializzazione. {1} potrebbe significare molte cose. Quando applicato a un int, lo riempie con un 1. Quando applicato a un std::vector<int>, significa creare un elemento unico vector, con 1 nel primo elemento. E così via.

Quando si chiama una funzione modello di chi è completamente privo di vincoli, non vi sono informazioni sul tipo per l'inizializzazione uniforme con cui lavorare. E senza le informazioni sul tipo, l'inizializzazione uniforme non può funzionare.

Ad esempio:

bar({{0,1}}); 

ci si aspetta che questo sia di tipo std::initializer_list<std::pair<int,int>>. Ma come potrebbe sapere il compilatore? Il primo parametro di bar è un modello non vincolato; può letteralmente essere qualsiasi tipo. Cow potrebbe il compilatore intuire che intendi questo tipo specifico?

Molto semplicemente, non è possibile. I compilatori sono bravi, ma non sono chiaroveggenti. L'inizializzazione uniforme può funzionare solo in presenza di informazioni sul tipo e i modelli non vincolati rimuovono tutto ciò.

Con tutti i diritti, quella riga non avrebbe dovuto essere compilata, secondo C++ 11. Non è in grado di dedurre quale tipo si desiderava che fosse lo {...}, quindi avrebbe dovuto fallire. Sembra un bug GCC o qualcosa del genere.

+0

Questo non funzionerà perché il compilatore non ha idea di cosa dedurrà 'T' se ha solo' std :: pair 'e' {1, 2} '. Mi piace come descrivi il problema nella prima parte della tua risposta. Lo stesso ragionamento vale per come ottenere 'T' per' coppia ':) –

+0

@ JohannesSchaub-litb: abbastanza giusto. Rimosso. –

Problemi correlati