2015-07-03 16 views
6

Si consideri il seguente frammento di codice:Perché scalare tra parentesi non sono interpeted come initializer_list

#include <iostream> 
#include <initializer_list> 

struct C 
{ 
    C(std::initializer_list<int>) { std::cout << "list\n"; } 
    C(std::initializer_list<int>, std::initializer_list<int>) { std::cout << "twice-list\n"; } 
}; 

int main() 
{ 
    C c1 { {1,2}, {3} }; // twice-list ctor 
    C c2 { {1}, {2} }; // why not twice-list ? 
    return 0; 
} 

Live demo.

Perché i valori scalari nelle parentesi per la variabile c2 non vengono interpretati come std :: initializer_list separato?

risposta

4

In primo luogo, qualcosa di molto importante: si hanno due diversi tipi di costruttori. Il primo in particolare, C(std::initializer_list<int>), è chiamato inizializzatore-elenco costruttore. Il secondo è solo un normale costruttore definito dall'utente.

[dcl.init.list]/p2

Un costruttore è un inizializzatore-list costruttore se primo parametro è di tipo std::initializer_list<E> o riferimenti eventualmente cv qualificato std::initializer_list<E> per un certo tipo E, e non ci sono altri parametri o tutti gli altri parametri hanno argomenti predefiniti (8.3.6).

In un elenco inizializzazione contenente uno o più inizializzazione-clausole, initializer-list costruttori sono considerati prima di ogni altri costruttori. Cioè, i costruttori di inizializzatore-lista sono inizialmente gli unici candidati durante la risoluzione di sovraccarico.

[over.match.list]/p1

Quando gli oggetti di tipo non-aggregato classe T sono elenco-inizializzato tale che 8.5.4 specifica che la risoluzione di sovraccarico viene eseguita secondo le regole in questa sezione , la risoluzione di sovraccarico seleziona il costruttore in due fasi:

  • Inizialmente, le funzioni candidate sono i costruttori inizializzatore-list (8.5.4) della classe T e la lista degli argomenti consiste nella lista di inizializzazione come singolo argomento .

  • Se non viene trovato nessun costruttore di inizializzazione-list praticabile, la risoluzione di sovraccarico viene eseguita di nuovo, dove le funzioni candidati sono tutti i costruttori della classe T e la lista degli argomenti è costituito da elementi della lista di inizializzazione.

Così per entrambe le dichiarazioni di c1 e c2 il set candidato consiste solo del costruttore C(std::initializer_list<int>).

Dopo aver selezionato il costruttore, gli argomenti vengono valutati per verificare se esiste una sequenza di conversione implicita per convertirli nei tipi di parametro. Questo ci porta alle regole per conversioni di inizializzazione-list:

[over.ics.list]/p4 (sottolineatura mia):

In caso contrario, se il tipo di parametro è std::initializer_list<X>e tutti gli elementi di l'elenco di inizializzazione può essere convertito implicitamente in X, la sequenza di conversione implicita è la peggiore conversione necessaria per convertire un elemento nell'elenco a X o se l'elenco di inizializzazione non ha elementi, la conversione di identità.

Ciò significa che esiste una conversione se ogni elemento dell'elenco di inizializzazione può essere convertito in int.

attenzione di Let su c1 per ora: Per l'inizializzazione-list {{1, 2}, {3}}, l'inizializzatore-clausola di {3} può essere convertito in int ([over.ics.list] /p9.1), ma non {1, 2} (cioè int i = {1,2} è malato -formed). Ciò significa che la condizione per la citazione sopra è violata. Poiché non v'è alcuna conversione, la risoluzione di sovraccarico non riesce dal momento che non ci sono altri costruttori vitali e ci sono prese di nuovo alla seconda fase del [over.match.list]/p1:

  • Se non praticabile di inizializzazione-list costruttore viene trovato, viene eseguita nuovamente la risoluzione di sovraccarico, dove le funzioni candidate sono tutti i costruttori della classe T e l'elenco degli argomenti è costituito dagli elementi dell'elenco di inizializzazione.

Si noti il ​​cambiamento nella formulazione alla fine. L'elenco degli argomenti nella seconda fase non è più un singolo elenco inizializzatore, ma gli argomenti dell'elenco di controventi utilizzato nella dichiarazione. Ciò significa che possiamo valutare le conversioni implicite in termini di liste di inizializzazione singolarmente anziché nello stesso momento.

Nella inizializzatore-list {1, 2}, entrambi inizializzazione-clausole possono essere convertiti int, così l'intero inizializzatore-clausola può essere convertito initializer_list<int>, uguale per {3}. La risoluzione del sovraccarico viene quindi risolta con la scelta del secondo costruttore.

Ora concentriamoci su c2, che dovrebbe essere facile ora. Il costruttore di inizializzatore-elenco viene valutato per la prima volta e, utilizzando { {1}, {2} }, esiste sicuramente una conversione inda {1} e {2}, quindi viene scelto il primo costruttore.

+0

Quindi, Perchè se cambierò il secondo costruttore in 'C (std :: initializer_list , std :: initializer_list )' e 'c2' in' C c2 {{1.0}, {2}}; 'Otterrò il compilatore errore? Secondo la tua risposta deve essere selezionato il secondo costruttore per questo. O mi sono fatto una conclusione sbagliata? [live demo] (http://coliru.stacked-crooked.com/a/e299e66462e220cf) – alexolut

+1

@alexolut [over.isc.list]/p4: "[...] e tutti gli elementi dell'elenco di inizializzazione possono essere implicitamente convertito in 'X'," A 'double' può essere implicitamente convertito in' int', quindi questa clausola vale. Leggendo ulteriormente: "la sequenza di conversione implicita è la peggiore conversione necessaria per convertire un elemento di della lista in' X' ". Questa sequenza di conversione implicita è la * sequenza di conversione restringente *. Quindi non è che non sia stato trovato un costrutto elenco inizializzatore valido (passare alla fase 2 significa che non ne abbiamo trovato uno), è che la conversione stessa causa la malformazione del programma. – 0x499602D2

+0

I.e. è stato trovato un costruttore valido, ma la conversione dell'argomento non è valida (mal formata). In questo caso non siamo arrivati ​​alla seconda fase. - Capisco, è corretto? – alexolut

0

C c2 { {1}, {2} };

Questa linea non passa in due argomenti di std::initializer_list<int>, ma piuttosto, sta passando in uno std::initializer_list<std::initializer_list<int> >. Una soluzione potrebbe essere quella di istanziare invece c2 in questo modo:

C c2({1}, {2});

+1

La domanda è "perché". –

+0

Vedo. Consentitemi di prendere un riferimento veramente veloce e modificare. – ross

+0

Fondamentalmente, la risposta a "perché" è perché std :: initializer_list ha una priorità speciale quando appare in un costruttore.Se hai un costruttore con una std :: initializer_list e costruisci l'oggetto con l'inizializzazione delle parentesi, il compilatore farà del suo meglio per convertire ciò che hai inserito per adattarlo al costruttore std :: initializer_list, anche se non è la soluzione migliore – KABoissonneault

Problemi correlati