8

Dato il seguente programma:ambiguo string :: operator = chiamata per il tipo con la conversione implicita a int e la stringa

#include <iostream> 
#include <string> 
using namespace std; 
struct GenericType{ 
    operator string(){ 
     return "Hello World"; 
    } 
    operator int(){ 
     return 111; 
    } 
    operator double(){ 
     return 123.4; 
    } 
}; 
int main(){ 
    int i = GenericType(); 
    string s = GenericType(); 
    double d = GenericType(); 
    cout << i << s << d << endl; 
    i = GenericType(); 
    s = GenericType(); //This is the troublesome line 
    d = GenericType(); 
    cout << i << s << d << endl; 
} 

Compila il Visual Studio 11, ma non clang o gcc. E sta avendo difficoltà perché vuole convertire in modo implicito da un GenericType a un int ad un char ma potrebbe anche restituire un string e quindi v'è un'ambiguità (operator=(char) e operator=(string) sia partita GenericType).

Tuttavia, il costruttore di copie funziona correttamente.

La mia domanda è: come posso risolvere questa ambiguità senza modificare il contenuto di main? Cosa devo fare per modificare GenericType per gestire questa situazione?

+4

Le conversioni implicite sono una buona fonte di problemi. Riconsiderare se vuoi davvero questo ... –

+0

lo faccio. Sono principalmente interessato a questo per curiosità a questo punto. – M2tM

+2

Si può anche usare l'operatore esplicito int() 'etc in C++ 11. Ciò impedisce gli errori tanto quanto l'uso della funzione 'getType()', poiché l'utente deve eseguire esplicitamente il cast. – chris

risposta

9

Credo che gcc e clang siano corretti.

Ci sono due operator= sovraccarichi in gioco:

string& operator=(string const& str); // (1) 
string& operator=(char ch);   // (2) 

Entrambe queste operator= sovraccarichi richiedono una conversione definita dall'utente dal proprio argomento di tipo GenericType. (1) richiede l'utilizzo della conversione in string. (2) richiede l'utilizzo della conversione in int, seguita da una conversione standard in char.

L'importante è che entrambi i sovraccarichi richiedano una conversione definita dall'utente. Per determinare se una di queste conversioni è migliore rispetto agli altri, possiamo guardare alle regole di risoluzione di sovraccarico, in particolare la seguente regola da C++ 11 §13.3.3.2/3 (riformattato per chiarezza):

utente -defined sequenza conversione U1 è una migliore sequenza conversione di un'altra sequenza di conversione definita dall'utente U2 se

  1. che contengono la stessa funzione definita dall'utente conversione o costruttore o inizializzazione aggregata e

  2. la seconda sequenza di conversione standard di U1 è migliore della seconda sequenza di conversione standard di U2.

Nota che un e unisce le due parti della norma, in modo da entrambe le parti devono essere soddisfatti. La prima parte della regola non è soddisfatta: le due sequenze di conversione definite dall'utente utilizzano diverse funzioni di conversione definite dall'utente.

Pertanto, nessuna conversione è migliore e la chiamata è ambigua.

[Non ho un buon suggerimento su come risolvere il problema senza cambiare la definizione di main(). Le conversioni implicite di solito non sono una buona idea; a volte sono molto utili, ma più frequentemente possono causare ambiguità di sovraccarico o altri strani comportamenti di sovraccarico.]

c'era un bug report gcc in cui questo problema è stato descritto e risolto come da disegno: compiler incorrectly diagnoses ambigous operator overload.

+0

Vorrei indicare che in qualche modo Visual Studio riesce a trattare correttamente anche questo: string val; val = 65; cout << (val == 'A'); Mi chiedo se si tratta di una vera differenza di risoluzione del compilatore. Sospetto che tu abbia ragione sul fatto che gcc e clang siano corretti, suppongo che seguirò un bug con microsoft e vedrò se è così. Speravo davvero in qualche tipo di trucco indiretto per gestire correttamente il sovraccarico assegnando direttamente a un int senza bisogno di un cast, ma non consentendo la conversione implicita se i tipi non corrispondono esattamente. – M2tM

+1

https://connect.microsoft.com/VisualStudio/feedback/dettagli/743685/std-string-assignment-should-probably-be-ambiguous-and-fail-to-compile-but-is-not # tabs – M2tM

+0

Microsoft ha confermato che uno dei suoi sviluppatori ha confermato questo difetto . Penso che sia un peccato personalmente, penso che la definizione dello standard di risoluzione del sovraccarico potrebbe probabilmente fare un lavoro migliore risolvendo personalmente questo tipo di ambiguità. Nel caso di due catene di chiamata che coinvolgono un cast specificato dall'utente, quello che risulta nel risultato preciso potrebbe facilmente essere considerato il migliore (oltre uno che richiede un ulteriore cast tra i tipi di pod). – M2tM

2

Credo GCC è sbagliato. Nel libro "The C++ Programming Language" di Bjarne Stroustrup, c'è un intero capitolo dedicato al sovraccarico dell'operatore. Nella sezione 11.4.1, il primo paragrafo dice questo:

"Un'assegnazione di un valore di tipo V a un oggetto di classe X è legale se esiste un operatore di assegnazione X :: operator = (Z) in modo che V è Z o c'è una conversione univoca da V a Z. L'inizializzazione è trattata in modo equivalente. "

Nell'esempio, GCC accetta "string s = GenericType();" tuttavia rifiuta "s = GenericType();", quindi ovviamente non tratta il compito allo stesso modo dell'inizializzazione. Questo è stato il mio primo indizio che qualcosa non va nel GCC.

GCC riporta 3 candidati per la conversione di GenericType in stringa nell'assegnazione, il tutto in basic_string.h. Uno è la conversione corretta, uno che segnala non è valido, e il terzo causa l'ambiguità. Questo è il sovraccarico del operatore basic_string.h che causa l'ambiguità:

/** 
* @brief Set value to string of length 1. 
* @param c Source character. 
* 
* Assigning to a character makes this string length 1 and 
* (*this)[0] == @a c. 
*/ 
basic_string& operator=(_CharT __c) { 
    this->assign(1, __c); 
    return *this; 
} 

questa non è una conversione valida perché accetta un operando che non corrisponde al tipo di oggetto che viene passato a esso. In nessun posto si tratta di un incarico a un personaggio tentato, quindi questa conversione non dovrebbe essere un candidato per niente meno che causa ambiguità. Sembra che GCC mischi il tipo di modello con il tipo di operando nel suo membro.

EDIT: Non ero consapevole del fatto che l'assegnazione di un intero in una stringa era infatti legale, come un intero può essere convertito in un char, che può essere assegnato ad una stringa (anche se una stringa non può essere inizializzata a un salmerino!). GenericType definisce una conversione in un int, rendendo così questo membro un candidato valido. Tuttavia, continuo a sostenere che non si tratta di una conversione valida, poiché l'utilizzo di questa conversione comporterebbe due conversioni implicite definite dall'utente per l'assegnazione, prima da GenericType a int, quindi da Int a String. Come affermato nello stesso libro 11.4.1, "solo un livello di conversione implicita definita dall'utente è legale."

+0

Credo che il motivo per cui non ha esito negativo durante la costruzione in GCC non è a causa di incoerenza interna con il compilatore, ma perché la classe basic_string non ha un costruttore che accetta _CharT (solo un operatore di assegnazione che lo fa). Ho il sospetto che se così fosse, anche questo sarebbe fallito in fase di costruzione. – M2tM

+0

Ciò potrebbe essere dovuto al fatto che l'inizializzazione di un char in una stringa costituisce un'eccezione a questa regola. L'inizializzazione di una stringa con un carattere non è valida, tuttavia l'assegnazione di un carattere a una stringa è valida, come indicato nello stesso libro nella sezione 20.3.7. – endoalir

+0

Il valore da confrontare è di tipo stringa, tuttavia l'operando per questo overload prende un carattere. Affinché questo sia un candidato accettabile, un char dovrebbe essere inizializzato con il valore di una stringa. Come: char c = string (v). Non ha alcun senso! Sebbene sia possibile assegnare un carattere a una stringa, non è possibile assegnare una stringa a un carattere, che è la conversione necessaria per questo candidato. Siccome questa conversione non è valida, dovrebbe essere questo candidato e quindi non ci dovrebbero essere ambiguità. – endoalir

1

La mia domanda è: come posso risolvere questa ambiguità senza modificare il contenuto di main?

creare una classe denominata string che non dispone di un ambiguo operator= e poi non lo fanno using il std uno.

Ovviamente questa non è una soluzione molto buona, ma funziona e non è necessario modificare main.

Non penso che si possa ottenere il comportamento desiderato in un altro modo.

+0

Questo è accurato, ma non così utile come la domanda alla quale alla fine ho assegnato la taglia. Grazie, tuttavia, hai fornito una risposta corretta e ragionevole. – M2tM

Problemi correlati