2016-01-05 10 views
19

Si consideri il seguente:Chiamare un costruttore esplicito con un elenco di inizializzazione: ambiguo o no?

struct A { 
    A(int, int) { } 
}; 

struct B { 
    B(A) { }     // (1) 
    explicit B(int, int) { } // (2) 
}; 

int main() { 
    B paren({1, 2}); // (3) 
    B brace{1, 2};  // (4) 
} 

La costruzione di brace in (4) in modo chiaro e senza ambiguità le chiamate (2). Sul clang, la costruzione di paren in (3) chiama in modo inequivocabile (1) dove come il gcc 5.2, non riesce a compilare con:

main.cpp: In function 'int main()': 
main.cpp:11:19: error: call of overloaded 'B(<brace-enclosed initializer list>)' is ambiguous 
    B paren({1, 2}); 
       ^
main.cpp:6:5: note: candidate: B::B(A) 
    B(A) { } 
    ^
main.cpp:5:8: note: candidate: constexpr B::B(const B&) 
struct B { 
     ^
main.cpp:5:8: note: candidate: constexpr B::B(B&&) 

quale compilatore è giusto? Ho il sospetto clang è corretto qui, come l'ambiguità in gcc può nascere solo attraverso un percorso che coinvolge implicitamente la costruzione B{1,2} e passaggio che per il costruttore di copia/spostamento - ma che il costruttore è segnato explicit, in modo tale costruzione implicita non dovrebbe essere consentito.

+2

MSVS 2015 compila anche questo. – NathanOliver

+2

Sembra molto simile alla descrizione del problema qui: https://gcc.gnu.org/ml/gcc-help/2014-02/msg00004.html che ha portato a http://gcc.gnu.org/bugzilla/show_bug .cgi? id = 60027 che è irrisolto/non commentato finora. – kfunk

+1

Interessante. Copy-list-init dovrebbe considerare tutti i costruttori e essere mal formato se viene scelto un costruttore esplicito. La domanda è se questo significa che una sequenza di conversione implicita non può essere formata, o se può essere formata e quindi l'inadeguatezza entra in gioco. Nel primo caso non sarebbe ambiguo; nel secondo caso è ambiguo. –

risposta

8

Per quanto posso dire, questo è un bug clang.

L'inizializzazione delle liste di copia ha un comportamento piuttosto non intuitivo: considera i costruttori espliciti come vitali fino a quando la risoluzione di sovraccarico non è completamente terminata, ma può quindi rifiutare il risultato di sovraccarico se viene scelto un costruttore esplicito. La formulazione in un post-N4567 progetto, [over.match.list] p1

In copia-list-inizializzazione, se viene scelta un costruttore explicit, l'inizializzazione è mal-formata. [Nota: Questo differisce dalle altre situazioni (13.3.1.3, 13.3.1.4), dove solo i costruttori di conversione sono considerati per l'inizializzazione della copia.Questa limitazione si applica solo a se questa inizializzazione fa parte del risultato finale della risoluzione di sovraccarico . - nota end]


clang TESTA accetta il seguente programma:

#include <iostream> 
using namespace std; 

struct String1 { 
    explicit String1(const char*) { cout << "String1\n"; } 
}; 
struct String2 { 
    String2(const char*) { cout << "String2\n"; } 
}; 

void f1(String1) { cout << "f1(String1)\n"; } 
void f2(String2) { cout << "f2(String2)\n"; } 
void f(String1) { cout << "f(String1)\n"; } 
void f(String2) { cout << "f(String2)\n"; } 

int main() 
{ 
    //f1({"asdf"}); 
    f2({"asdf"}); 
    f({"asdf"}); 
} 

Che è, tranne che per commentare la chiamata a f1, direttamente da Bjarne Stroustrup di N2532 - Uniform initialization, Capitolo 4. Grazie a Johannes Schaub per avermi mostrato questo articolo su std-discussion.

Lo stesso capitolo contiene la seguente spiegazione:

Il vero vantaggio di explicit è che rende f1("asdf") un errore . Un problema è che la risoluzione di sovraccarico "preferisce" i costruttori non , in modo che f("asdf") chiami f(String2). Considero la risoluzione di f("asdf") meno ideale, perché lo scrittore di String2 probabilmente non intendeva risolvere le ambiguità a favore di String2 (almeno non in tutti i casi in cui espliciti e non esplicite costruttori si verificano come questo) e il scrittore di String1 certamente no. La regola favorisce i "programmatori sciatti" che non usano explicit.


Per quanto ne so, N2640 - Initializer Lists — Alternative Mechanism and Rationale è l'ultima carta che include logica di questo tipo di risoluzione di sovraccarico; il successore N2672 è stato votato nella bozza C++ 11.

Dal suo capitolo "The Meaning Of Explicit":

Un primo approccio per fare l'esempio mal formata è quella di richiedere che tutte le costruttori (esplicite e non espliciti) sono considerati per impliciti conversioni , ma se un costruttore esplicito finisce per essere selezionato, quel programma è mal formato. Questa regola può introdurre le sue sorprese; per esempio:

struct Matrix { 
    explicit Matrix(int n, int n); 
}; 
Matrix transpose(Matrix); 

struct Pixel { 
    Pixel(int row, int col); 
}; 
Pixel transpose(Pixel); 

Pixel p = transpose({x, y}); // Error. 

Un secondo approccio è quello di ignorare i costruttori espliciti quando si cerca per la vitalità di una conversione implicita, ma di includere quando in realtà la selezione del costruttore di conversione: se un costruttore esplicito finisce essendo selezionato, il programma è mal formato. Questo approccio alternativo consente di utilizzare l'ultimo esempio (Pixel-vs-Matrix) come previsto (transpose(Pixel) è selezionato), mentre l'esempio originale ("X x4 = { 10 };") è mal formato.

Mentre questo documento propone di utilizzare il secondo approccio, la sua formulazione sembra essere viziata - nella mia interpretazione del testo, non produce il comportamento descritto nella parte razionale della carta. La formulazione è stata rivista in N2672 per utilizzare il primo approccio, ma non sono riuscito a trovare alcuna discussione sul perché questo è stato modificato.


C'è naturalmente un po 'più formulazione coinvolti in inizializzazione di una variabile come nel PO, ma considerando la differenza di comportamento tra clang e gcc è la stessa del primo programma di esempio nella mia risposta, credo che questo coperchi i punti principali.

+1

Ho inserito un bug clang: https://llvm.org/bugs/show_bug.cgi?id=27642 – ecatmur

0

Questa non è una risposta completa, anche se è troppo lunga come un commento.
Cercherò di proporre un controesempio per il tuo ragionamento e io sono pronto a vedere downvote perché io sono lontano dall'essere certo.
Comunque proviamo !! :-)

Ne consegue l'esempio ridotta:

struct A { 
    A(int, int) { } 
}; 

struct B { 
    B(A) { } 
    explicit B(int, int) { } 
}; 

int main() { 
    B paren({1, 2}); 
} 

In questo caso, l'istruzione {1, 2} dà luogo apparentemente due soluzioni:

  • inizializzazione diretta mediante B(A), perché A(int, int) non è esplicito, e quindi è consentito e che in realtà è il primo candidato

  • per lo stesso motivo di cui sopra, può essere interpretato come B{B(A{1,2})} (beh, mi permetta di abusare la notazione per dare un'idea e quello che voglio dire), che è {1,2} permette la costruzione di un oggetto temporaneo B che viene utilizzato subito dopo come argomento per il costruttore di copia/spostamento, ed è consentito ancora una volta perché i costruttori coinvolti non sono espliciti

quest'ultimo spiegherebbe il secondo e il terzo candidati.

Ha senso?
Sono pronto a cancellare le risposte fintanto che mi spieghi cosa c'è di sbagliato nel mio ragionamento. :-)

+2

Il secondo interrompe la regola di conversione definita dall'utente. –

+0

Mi fido di te, ma puoi essere più dettagliato? Sospetto che dovrò imparare qualcosa di nuovo qui. Grazie. – skypjack

+0

Lo standardese è sparpagliato dappertutto, ma l'idea di base è che nella formazione di una sequenza di conversione è possibile utilizzare al massimo una conversione definita dall'utente. –

Problemi correlati