2013-06-08 8 views
8
#include <iostream> 
using namespace std; 
class Myclass{ 
     private: 
       int i; 
     public: 
       template<typename U>Myclass(U& lvalue):i(lvalue){cout<<i <<" template light reference" <<endl;i++;} 
       //Myclass(Myclass &lvalue):i(lvalue){cout<<i <<" light reference" <<endl;i++;} 
       template<typename U>Myclass(U&& rvalue):i(rvalue){cout<<i <<" template right reference" <<endl;i++;} 
}; 

int main(int argc,char*argv[]) 
{ 
Myclass a(0); 
Myclass b(a); 
Myclass c(2); 
return 0; 
} 

errore:Perché i miei costruttori di copia T & e T && sono ambigui? messaggio

rightvalue.cpp: In function ‘int main(int, char**)’: 
rightvalue.cpp:15:12: error: call of overloaded ‘Myclass(Myclass&)’ is ambiguous 
rightvalue.cpp:15:12: note: candidates are: 
rightvalue.cpp:10:23: note: Myclass::Myclass(U&&) [with U = Myclass&] 
rightvalue.cpp:8:23: note: Myclass::Myclass(U&) [with U = Myclass] 
rightvalue.cpp:4:7: note: constexpr Myclass::Myclass(const Myclass&) 
rightvalue.cpp:4:7: note: constexpr Myclass::Myclass(Myclass&&) <near match> 
rightvalue.cpp:4:7: note: no known conversion for argument 1 from ‘Myclass’ to ‘Myclass&&’ 
+5

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57172 –

+0

@MarkTolonen cambiando a 'const U &' risulta l'output di 'modello di riferimento a destra' – yuan

+0

Il bug è stato corretto per 4.9, sentiti libero giocarci e segnalare eventuali problemi riscontrati con il nuovo comportamento. –

risposta

9

Così qui è quello che succede (o meglio, dovrebbe succedere): al fine di risolvere questo chiamata al costruttore

Myclass b(a); 

Il compilatore deve eseguire la risoluzione di sovraccarico e decidere, in un primo momento, quali costruttori sono candidati validi.

La prima cosa da notare è che entrambi i costruttori sono vitali: forme come T&& fanno non risolvere sempre in riferimenti rvalue (che è solo il caso se ciò che si sta passando è un rvalue). Questo è ciò che Scott Meyers chiama "universal references" (nota che questo termine non è standard).

Quando il compilatore tenta di eseguire il tipo detrazione per vedere se il secondo costruttore è vitale, il tipo T in questo caso sarà dedotto di essere Myclass& - poiché ciò che si sta passando (a) è un lvalue; e a causa delle regole di compressione di riferimento, Myclass& && fornisce Myclass&, quindi si ottiene la stessa firma del primo costruttore.

Quindi la chiamata è ambigua? Come sottolineato da Marc Glisse in the comments to the question, e da Jonathan Wakely in the comments to this answer, no, non dovrebbe essere (come ha sostenuto la versione originale di questa risposta - mea culpa).

Il motivo è che una regola speciale nello standard specifica che il sovraccarico che accetta un riferimento di lvalue è più specializzato rispetto al sovraccarico che accetta un riferimento di rvalue. Al punto 14.8.2.4/9 del C++ 11 standard:

Se, per un dato tipo, deduzione riesce in entrambe le direzioni (cioè, i tipi sono identici dopo le trasformazioni sopra) e sia P e a fosse tipi di riferimento (prima di essere sostituito con il tipo di cui sopra):

- se il tipo dal modello argomento era un riferimento Ivalue e il tipo dal modello parametro non è stato, il tipo di argomento è considerato per essere più specializzati rispetto agli altri; in caso contrario, [...]

Questo significa che il compilatore ha un bug (the link to the bug report è stato fornito da Marc Glisse nei commenti alla domanda).

per risolvere questo bug e assicurarsi che il modello costruttore di accettare un T&& sarà scelto da GCC solo quando vengono passati rvalues, è possibile riscrivere in questo modo:

#include <type_traits> 

    template<typename U, 
     typename std::enable_if< 
      !std::is_reference<U>::value 
      >::type* = nullptr> 
    Myclass(U&& rvalue):i(rvalue) 
    {cout<<i <<" template right reference" <<endl;i++;} 

Dove ho aggiunto uno SFINAE-constraint il che rende il compilatore scartare questo costruttore dal set di overload quando viene passato un lvalue.

Quando viene passato un valore assegnabile, infatti, T sarà dedotto di essere X& per qualche X (il tipo di espressione si passa, Myclass nel tuo caso), e T&& sarà risolvere in X&; quando viene passato un valore, d'altra parte, viene dedotto da da X (il tipo dell'espressione che si passa, Myclass nel tuo caso) e T&& verrà risolto in X&&.

Dal momento che i controlli SFINAE vincolo siano essi T non si deduce di essere un tipo di riferimento e crea un fallimento di sostituzione in caso contrario, il vostro costruttore è garantito per essere preso in considerazione solo quando l'argomento è un'espressione rvalue.

Quindi, per riassumere il tutto:

#include <iostream> 
#include <type_traits> 

class Myclass 
{ 
    int i; 
public: 
    template<typename U> 
    Myclass(U& lvalue):i(lvalue) 
    { 
     std::cout << i <<" template light reference" << std::endl; 
     i++; 
    } 

    template<typename U, 
     typename std::enable_if< 
      !std::is_reference<U>::value 
      >::type* = nullptr> 
    Myclass(U&& rvalue):i(rvalue) 
    { 
     std::cout << i <<" template right reference" << std::endl; 
     i++; 
    } 
}; 

int main(int argc,char*argv[]) 
{ 
    Myclass a(0); 
    int x = 42; 
    Myclass b(x); 
    Myclass c(2); 
} 

Ecco un live example.

+2

Tuttavia, come nota @MarcGlisse, [DR 1164] (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1164) dice che il sovraccarico del riferimento lvalue dovrebbe essere più specializzato del " riferimento universale "one –

+0

@JonathanWakely: Grazie! Non ero a conoscenza di questo. Dovrò modificare la mia risposta –

Problemi correlati