2015-02-26 8 views
5

perché il seguente codice non viene compilato e quando rimuovo la parola chiave esplicita prima del costruttore in classe A, viene compilato?Influenza dei costruttori "espliciti" nella risoluzione di sovraccarico

Utilizzando Visual Studio 2013:

enum E { e1_0, e1_1 }; 

template<typename T> 
struct A 
{ 
    A() {} 
    explicit A(unsigned long) {} 
    A(T) {} 
}; 

struct B 
{ 
    B() {} 
    B(E) {} 
}; 


void F(B) {}; 
void F(A<short>) {}; 

void test() 
{ 
    F(e1_0); 
} 

Errore:

1>------ Build started: Project: exp_construct_test, Configuration: Debug Win32 ------ 
1> exp_construct_test.cpp 
1>e:\exp_construct_test\exp_construct_test.cpp(23): error C2668: 'F' : ambiguous call to overloaded function 
1>   e:\exp_construct_test\exp_construct_test.cpp(19): could be 'void F(A<short>)' 
1>   e:\exp_construct_test\exp_construct_test.cpp(18): or  'void F(B)' 
1>   while trying to match the argument list '(E)' 
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ========== 

Edit: Ho scaricato clang clang e compilato con-cl, che segnala l'errore per entrambi i casi. Quindi, come è stato sottolineato nei commenti, l'ambiguità è compresa tra A<short>(short) e B(E).

Così, forse, c'è un bug in VC++, che quando rimuovo explicit dal A(unsigned long), il compilatore con qualsiasi intenzione sceglie B (E) invece di generare un errore ambiguouity. Qualcuno può confermare il comportamento clang come standard compatibile e VC++ come bug?

ho aggiunto

void G(E) {}; 
void G(short) {}; 

e una chiamata a G come questo:

G(e1_0); 

che non solleva alcun errore. Perché qui G(E) è prefferrato e, in caso di candidati A<short>::A(short) e B::B(E), sono ambigui?

Fine Edit

Grazie --joja

+2

Se rimuovo 'explicit', non viene compilato neanche, il che non dovrebbe essere. La chiamata ambigua non è per 'A :: A' ma per' F'. Nota che 'A :: A (T)' non è 'esplicito' quindi la chiamata a' F' è ambigua. Se tu * aggiungi * un 'esplicito' a' A :: A (T) 'governerà la conversione e il codice verrà compilato, comunque. – 5gon12eder

+0

La parola chiave esplicita di fronte a un costruttore impedisce l'istanza di un oggetto di quel tipo implicitamente con solo un parametro che agisce come un argomento costruttore. Quando rimuovi "esplicito" dal costruttore di A, puoi costruire A semplicemente passando "void F (A )" a breve, che è quello che F (e1_0) sembra fare – Prismatic

+0

@ 5gon12eder, quale compilatore stai usando? In VS2013 si compila senza 'esplicito'. So che la chiamata ambigua è per 'F', ma la causa è la conversione di' e1_0' in un tipo che un sovraccarico di 'F' accetta. Quando 'explicit' viene rimosso, il compilatore sceglie' B (E) 'per fare la conversione e chiama' F (B) '. Ho aggiunto 'esplicito' per essere sicuro, che non uso implicitamente il costruttore, che è un costruttore caso speciale nel mio codice reale. A mio parere, l'uso di 'explicit' dovrebbe guidare ancora di più il compilatore per utilizzare la conversione B (E) e non considerare una' enum E' per la promozione 'unsigned long'. – joja

risposta

3

Diamo un'occhiata alle varie varianti del tuo esempio una dopo l'altra.

  1. L'esempio originale chiamando f(e0).

    enum E {e0, e1}; 
    
    template<typename T> 
    struct A 
    { 
        A(); // (1) 
        explicit A(unsigned long); // (2) 
        A(T); // (3) 
    }; 
    
    struct B 
    { 
        B(); // (4) 
        B(E); // (5) 
    }; 
    
    void f(A<short>); // (6) 
    void f(B); // (7) 
    
    void g(E); // (8) 
    void g(short); // (9) 
    

    Tra le tre possibilità

    • convertono e0 a un unsigned long, creare un A<short> da esso tramite costruttore (2) e chiamare sovraccarico (6),
    • convertire e0-short, creare un A<hort> da esso tramite costruttore (3) e chiamata sovraccarico (6) e
    • creare un B da e0 tramite costruttore (5) e chiamata sovraccarico (7)

    la prima opzione non è applicabile poiché (2) è explicit. I rimanenti due comportano entrambi una conversione definita dall'utente che viene considerata ugualmente valida e nessuno viene preso in favore dell'altro. La chiamata è ambigua e il programma mal formato.

  2. Rimuovere la explicit dal costruttore e chiamare f(e0).

    template<typename T> 
    struct A 
    { 
        A(); // (1) 
        A(unsigned long); // (2) 
        A(T); // (3) 
    }; 
    
    struct B 
    { 
        B(); // (4) 
        B(E); // (5) 
    }; 
    

    Le tre opzioni rimangono gli stessi, ma questa volta, tutti e tre sono applicabili e la chiamata è (anche di più) ambiguo e il programma mal formati.

  3. Facciamo entrambi i costruttori explicit e chiamiamo f(e0).

    template<typename T> 
    struct A 
    { 
        A(); // (1) 
        explicit A(unsigned long); // (2) 
        explicit A(T); // (3) 
    }; 
    
    struct B 
    { 
        B(); // (4) 
        B(E); // (5) 
    }; 
    

    Ciò rende impossibile costruire implicitamente A<short> e la chiamata si riferisce inequivocabilmente a sovraccarico (5).

  4. Facciamo anche il costruttore Bexplicit e chiamiamo f(e0).

    template<typename T> 
    struct A 
    { 
        A(); // (1) 
        explicit A(unsigned long); // (2) 
        explicit A(T); // (3) 
    }; 
    
    struct B 
    { 
        B(); // (4) 
        explicit B(E); // (5) 
    }; 
    

    Questa volta, nessuno dei tre percorsi di conversione è applicabile perché ognuno sarebbe passare attraverso un costruttore explicit. Non c'è sovraccarico di f applicabile e il programma è mal formato.

  5. Chiama g(e0).

    Abbiamo due possibilità:

    • sovraccarico di chiamata (8) senza alcuna conversione o
    • convertono e0 ad un short e chiamare sovraccarico (9).

    Tra questi due, la prima opzione è chiaramente favorevole perché non comporta una conversione. La chiamata è inequivocabile. (Anche se il costruttore (5) non è explicit.)

Nota che i costruttori di default (1) e (4) in realtà non contribuiscono nulla a questa discussione. Test con GCC 4.9.1, tutti e cinque gli esempi si comportano come previsto.

+0

Grazie, @ 5gon12eder, per questa risposta. Ho alcune osservazioni: - Punto 2 .: con il costruttore (2) ** non ** contrassegnato esplicitamente, né in VisualC, né clang Ho tre possibili sovraccarichi segnalati. - Punto 1 .: Con (2) esplicito, chiamando f (e0) Mi aspettavo (5) per essere chiamato perché viene utilizzata solo la conversione costruttore definita dall'utente, E -> B (E), per (3) è necessario uno altro passo: E -> breve -> A (breve). - Quindi si conferma, Visual C++ è bacato quando compila un esempio del punto 2. senza generare alcun messaggio di errore e chiamando in silenzio B (E)? – joja

+0

@joja ** ad point 1: ** Questo potrebbe sembrare intuitivo per te, ma per quanto posso dire, non esiste una regola nello standard che classifica una qualsiasi di queste sequenze di conversione meglio dell'altro, quindi la chiamata è ambigua. ** ad point 2: ** Non mi affretto a sostenere che un compilatore a cui non ho accesso abbia un bug ma sono piuttosto fiducioso che il mio ragionamento e il comportamento di GCC siano corretti qui. Forse arriverà un avvocato di lingua più avanzato e commenterà con maggiore autorità su questo argomento. – 5gon12eder