2013-06-08 16 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; 
} 

błąd:Dlaczego moje konstruktory T & i kopiowania są niejednoznaczne? Komunikat

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 zmianie na '' const U & Wyniki wyjście 'szablonu prawo reference' – yuan

+0

Bug została ustalona na 4,9, nie krępuj grać z nim i zgłaszać wszelkie problemy z nowym zachowaniem. –

Odpowiedz

9

Więc tutaj jest to, co dzieje się (lub raczej powinny zdarzyć): aby rozwiązać ten wywołanie konstruktora

Myclass b(a); 

Kompilator musi Wykonaj analizę przeciążenia i zdecyduj, z początku, którzy konstruktorzy są realnymi kandydatami.

Pierwszą rzeczą jest, aby zauważyć, że zarówno konstruktorzy są opłacalne: Formy takie jak T&& zrobić nie zawsze rozwiązać w odnośnikach rvalue (to tylko w przypadku, gdy co jesteś przejazdem jest rvalue). To właśnie Scott Meyers nazywa "universal references" (zauważ, że ten termin nie jest standardem).

Gdy kompilator próbuje wykonać typu odliczenie aby sprawdzić, czy drugi konstruktor jest opłacalne, typ T w tym przypadku będzie wywnioskować być Myclass& - od czego przechodzą (a) jest lwartością; i ze względu na reguły zwijania odniesienia, Myclass& && daje Myclass&, więc kończy się na tym samym podpisu, który ma twój pierwszy konstruktor.

Czy połączenie jest niejednoznaczne? Jak wskazał Marc Glisse in the comments to the question, i przez Jonathan Wakely in the comments to this answer, nr, nie powinien on (jak pierwotna wersja tej odpowiedzi twierdził - mea culpa).

Powodem jest to, że szczególny przepis w Standard określa, że ​​przeciążenie akceptując lwartością odniesienia jest bardziej wyspecjalizowane niż przeciążenia przyjmująca odwołanie rvalue. Zgodnie z pkt 14.8.2.4/9 w C++ 11 Standard:

Jeśli dla danego typu, odliczenie powiedzie się w obu kierunkach (czyli typy są identyczne po przemianach powyżej) i zarówno P i a miały rodzaje odniesienia (przed zastąpione typu określonego powyżej):

- jeśli typ z szablonu argument lwartością odniesienia i rodzaju z szablonu parametr nie było, typ argument jest być bardziej wyspecjalizowanym niż inny; inaczej, [...]

Oznacza to, że kompilator ma błąd (the link to the bug report zostały przekazane przez Marca Glisse w komentarzach na pytanie).

Aby obejść ten problem i upewnić się, że szablon konstruktor przyjmując T&& będą zbierane przez GCC tylko gdy rvalues ​​są przekazywane, można przepisać to w ten sposób:

#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++;} 

Gdzie dodałem SFINAE-ograniczenie co powoduje, że kompilator odrzuca ten konstruktor z zestawu przeciążeniowego, gdy przekazywana jest wartość l.

Kiedy lwartością jest przekazywana, w rzeczywistości, T zostanie wyprowadzona być X& jakiegoś X (rodzaj ekspresji zdać, Myclass w Twoim przypadku), a T&& rozwiąże się X&; gdy zostanie przekazana wartość rundy, z drugiej strony, T zostanie wydedukowana jako X z jakiegoś X (typ wyrażenia, które przekazujesz, Myclass w twoim przypadku), a T&& rozwiąże się w X&&.

Ponieważ ograniczenie SFINAE sprawdza, czy T nie zostanie wydedukowane jako typ referencyjny i spowoduje niepowodzenie zastąpienia w inny sposób, Twój konstruktor będzie miał gwarancję, że zostanie uwzględniony tylko wtedy, gdy argument będzie wyrażeniem rvalue.

Więc Podsumowując to wszystko:

#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); 
} 

Oto live example.

+2

Ale jak zauważa @MarcGlisse, [DR 1164] (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1164) mówi przeciążenie odniesienia lwartości powinny być bardziej wyspecjalizowane niż " uniwersalne odniesienie "jeden –

+0

@ JonathanWakely: Dziękujemy! Nie wiedziałem o tym. Będę musiał edytować moją odpowiedź –

Powiązane problemy