2013-07-17 10 views
8

Ho un piccolo pezzo di codice C++ 11 che g ++ (4.7 o 4.8) rifiuta di compilare sostenendo che la chiamata al costruttore per B2 b2a (x, {P (y)}) è ambiguo. Clang ++ è felice con quel codice, ma rifiuta di compilare B2 b2b (x, {{P (y)}}) che g ++ è perfettamente felice di compilare!Risoluzione di overload del costruttore C++ 11 e initialiser_lists: clang ++ e g ++ non in accordo

Entrambi i compilatori sono perfettamente soddisfatti del costruttore B1 con {...} o {{...}} come argomento. Può un avvocato del linguaggio C++ spiegare quale compilatore è corretto (se presente) e cosa sta succedendo? Codice qui sotto:

#include <initializer_list> 

using namespace std; 

class Y {}; 
class X; 

template<class T> class P { 
public: 
    P(T); 
}; 

template<class T> class A { 
public: 
    A(initializer_list<T>); 
}; 

class B1 { 
public: 
    B1(const X&, const Y &); 
    B1(const X&, const A<Y> &); 
}; 

class B2 { 
public: 
    B2(const X &, const P<Y> &); 
    B2(const X &, const A<P<Y>> &); 
}; 

int f(const X &x, const Y y) { 
    B1 b1a(x, {y}); 
    B1 b1b(x, {{y}}); 
    B2 b2a(x, {P<Y>(y)}); 
    B2 b2b(x, {{P<Y>(y)}}); 
    return 0; 
} 

e gli errori del compilatore, clang:

$ clang++ -stdlib=libc++ -std=c++11 test-initialiser-list-4.cc -o test.o -c 
test-initialiser-list-4.cc:32:6: error: call to constructor of 'B2' is ambiguous 
    B2 b2(x, {{P<Y>(y)}}); 
    ^~~~~~~~~~~~~~~ 
test-initialiser-list-4.cc:26:5: note: candidate constructor 
    B2(const X &, const P<Y> &); 
    ^
test-initialiser-list-4.cc:27:5: note: candidate constructor 
    B2(const X &, const A<P<Y>> &); 
    ^

g ++:

test-initialiser-list-4.cc: In function 'int f(const X&, Y)': 
test-initialiser-list-4.cc:32:21: error: call of overloaded 'B2(const X&, <brace-enclosed initializer list>)' is ambiguous 
    B2 b2(x, {P<Y>(y)}); 
        ^
test-initialiser-list-4.cc:32:21: note: candidates are: 
test-initialiser-list-4.cc:27:5: note: B2::B2(const X&, const A<P<Y> >&) 
    B2(const X &, const A<P<Y>> &); 
    ^
test-initialiser-list-4.cc:26:5: note: B2::B2(const X&, const P<Y>&) 
    B2(const X &, const P<Y> &); 
    ^

Questa puzza come un'interazione tra inizializzazione uniforme, la sintassi lista initialiser e la funzione di sovraccaricare con template argomenti (che so che g ++ è abbastanza severo riguardo), ma non sono abbastanza un avvocato standard per essere in grado di decomprimere quello che dovrebbe essere il comportamento corretto qui!

+1

Due variabili locali con il nome di 'B2B' in 'f' la funzione è solo un errore di battitura e non la causa del problema, presumo ... – astraujums

+0

Anzi! Risolto il problema ... –

+1

Mi sembra che creare un 'A' da un inizializzatore_list e un' P' da un 'T' dovrebbero essere entrambi corrispondenze esatte, quindi l'ambiguità. Quello che non riesco a spiegare è perché i compilatori scelgono di cui lamentarsi. –

risposta

5

Primo codice, quindi quello che penso dovrebbe accadere. (In quanto segue, ignorerò il primo parametro, dal momento che ci interessa solo il secondo parametro: il primo è sempre una corrispondenza esatta nell'esempio). Si noti che le regole sono attualmente in flusso nelle specifiche, quindi non direi che uno o l'altro compilatore ha un bug.

B1 b1a(x, {y}); 

Questo codice non può chiamare il const Y& costruttore in C++ 11, perché Y è un aggregato e Y ha nessun membro di dati di tipo Y (ovviamente) o qualcos'altro initializable da essa (questa è una cosa brutta, e viene lavorato su per essere risolto - il CD C++ 14 non ha ancora una formulazione per questo, quindi non sono sicuro che l'ultimo C++ 14 conterrà questa correzione).

Il costruttore con il parametro const A<Y>& può essere chiamato - {y} sarà presa come argomento al costruttore di A<Y> e inizializzerà che il costruttore di std::initializer_list<Y>.

Quindi - secondo costruttore chiamato con successo.

B1 b1b(x, {{y}}); 

Qui, il fondo stesso argomento conta conta per il costruttore con il parametro const Y&.

Per il costruttore con parametro tipo const A<Y>&, è un po 'più complicato. La regola per il costo di conversione in risoluzione di sovraccarico che calcola il costo dell'inizializzazione di un std::initializer_list<T> richiede che ogni elemento dell'elenco di rinforzi sia convertibile in T. Tuttavia, abbiamo detto prima che {y} non può essere convertito in Y (poiché è un aggregato). Ora è importante sapere se std::initializer_list<T> è un aggregato o meno. Francamente, ho non ho idea di se deve essere considerato o meno un aggregato secondo le clausole della libreria Standard.

Se consideriamo un non aggregato, prenderemo in considerazione il costruttore di copie di std::initializer_list<Y>, che tuttavia attiverebbe nuovamente la stessa sequenza di test (portando a "ricorsione infinita" nel controllo della risoluzione di sovraccarico).Poiché questo è piuttosto strano e non implementabile, non penso che nessuna implementazione segua questo percorso.

Se consideriamo std::initializer_list un aggregato, diremo "no, nessuna conversione trovata" (vedere il numero di aggregati sopra riportato). In tal caso, dal momento che non possiamo chiamare il costruttore di inizializzatore con l'intero elenco di inizializzazione nel suo complesso, {{y}} verrà suddiviso in più argomenti e il costruttore/i di A<Y> prenderà ciascuno di questi separatamente. Quindi, in questo caso, avremmo finito con {y} inizializzando uno std::initializer_list<Y> come parametro singolo - che è perfettamente soddisfacente e funziona come un incantesimo.

Quindi supponendo che std::initializer_list<T> sia un aggregato, questo va bene e chiamare con successo il secondo costruttore.

B2 b2a(x, {P<Y>(y)}); 

In questo caso e l'altro caso, non abbiamo il problema di aggregazione, come sopra, con più Y, dal momento che ha un costruttore P<Y> fornito dall'utente.

Per il costruttore di parametri P<Y>, tale parametro verrà inizializzato da {P<Y> object}. Poiché P<Y> non ha elenchi di inizializzatori, l'elenco verrà suddiviso in singoli argomenti e chiama il costruttore di movimento P<Y> con un oggetto rvalue di P<Y>.

Per il costruttore parametro A<P<Y>>, è lo stesso del caso precedente con A<Y> inizializzato {y}: Da std::initializer_list<P<Y>> può essere inizializzato dal {P<Y> object}, la lista degli argomenti non è diviso, e quindi le graffe vengono utilizzati per inizializzatore che il costruttore di std::initializer_list<T> .

Ora, entrambi i costruttori funzionano correttamente. Si comportano come funzioni sovraccariche qui e il loro secondo parametro in entrambi i casi richiede una conversione definita dall'utente. Le sequenze di conversione definite dall'utente possono essere confrontate solo se in entrambi i casi viene utilizzata la stessa funzione di conversione o costruttore, non il caso qui. Quindi, questo è ambiguo in C++ 11 (e nel CD C++ 14).

noti che qui abbiamo un punto sottile per esplorare

struct X { operator int(); X(){/*nonaggregate*/} }; 

void f(X); 
void f(int); 

int main() { 
    X x; 
    f({x}); // ambiguity! 
    f(x); // OK, calls first f 
} 

Questo contatore risultato intuitivo sarà probabilmente fissato nella stessa seduta con fissa la stranezza aggregato inizializzazione sopra menzionato (sia chiamerà la prima f) . Questo viene implementato dicendo che {x}->X diventa una conversione di identità (come è X->x). Attualmente è una conversione definita dall'utente.

Quindi, ambiguità qui.

B2 b2b(x, {{P<Y>(y)}}); 

per il costruttore con il parametro const P<Y>&, abbiamo di nuovo dividere gli argomenti e ottenere {P<Y> object} argomento passato al costruttore (s) di P<Y>. Ricorda che lo P<Y> ha un costruttore di copie. Ma la complicazione qui è che non siamo autorizzati a usarlo (vedi 13.3.3.1p4), perché richiederebbe una conversione definita dall'utente. L'unico costruttore rimasto è quello che sta prendendo Y, ma un Y non può essere inizializzato da {P<Y> object}.

per il costruttore con il parametro A<P<Y>>, il {{P<Y> object}} possibile inizializzare una std::initializer_list<P<Y>>, perché {P<Y> object} è convertibile in P<Y> (se non con Y sopra - Dang, aggregati).

Quindi, secondo costruttore chiamato con successo.


Riassunto per tutti 4

  • secondo costruttore chiamato correttamente
  • sotto l'ipotesi che std::initializer_list<T> è un aggregato, questo va bene e chiamare il secondo costruttore successo
  • ambiguità qui
  • secondo costruttore chiamato con successo
+0

Grazie, sembra che la mia confusione non sia stata del tutto ingiustificata :) –

Problemi correlati