2011-09-11 10 views
5

Chciałbym podać konwersję z A<T>::B na A<U>::B.uogólniony konstruktor kopii w wewnętrznej klasie

template<typename T> 
struct A 
{ 
    struct B 
    { 
     B() {} 

     template<typename U> 
     B(const typename A<U>::B& rhs) {} 
    }; 
}; 

int main() 
{ 
    A<int>::B x; 
    A<double>::B y = x; 
} 

myślałem, że to zrobi, ale pojawia się błąd kompilatora:

conversion from ‘A<int>::B’ to non-scalar type ‘A<double>::B’ requested"

Dlaczego nie jest mój kodu poprawne, a co to jest poprawny sposób, aby osiągnąć żądaną konwersję?

+0

Dla wyjaśnienia, jaka jest pożądana konwersja? Ponieważ nie konwertujesz int do double, ale A :: B na A :: B, który jest klasą dla innej klasy. – SSight3

+0

@ SSight3: Pozwól mi zgadnąć, że Robert próbuje ponownie wprowadzić inteligentne wskaźniki z zewnętrznymi wartościami reflag i polimorficznymi przypisaniami ... – sehe

+0

Konwersja jest z A :: B do A :: B dla dowolnego T, U. int i double to tylko przykłady, których użyłem. I nie, to nie to robię. –

Odpowiedz

3

Nieznacznie zmienione źródło, aby wykazać, że jest to o granice w systemie typu odliczenia:

template<typename T> 
struct A 
{ 
    struct B 
    { 
     B() {} 

     template<typename U> 
      B(const typename A<U>::B& rhs) {} 

     template<typename U> 
      B& operator=(const typename A<U>::B& rhs) {} 

     template<typename U> 
      B& something(const typename A<U>::B& rhs) {} 
    }; 
}; 

int main() 
{ 
    A<int>::B x; 

    A<double>::B y(x);  // fails to deduce 
    A<double>::B y = x; // fails to deduce 
    A<double>::B y; y = x; // fails to deduce 

    x.something(y);  // fails to deduce 
    x.something<double>(y);// NO PROBLEM 
} 

Widać, że kiedy pomagamy kompilator trochę, nie ma już problemu. Należy także zwrócić uwagę rzeczywisty błąd kompilator (gcc) pokazuje, że to zamieszanie:

test.cpp|24 col 15| note: candidate is: 
test.cpp|15 col 44| note: template<class U> A<T>::B& A<T>::B::something(const typename A<U>::B&) 
[with U = U, T = int, A<T>::B = A<int>::B, typename A<U>::B = A<T>::B] 

Wskazówka część, gdzie U = U (tj nierozwiązany)

4

Szablon nie może być konstruktor kopia. §12.8/2, przypis:

Because a template constructor is never a copy constructor, the presence of such a template does not suppress the implicit declaration of a copy constructor. Template constructors participate in overload resolution with other constructors, including copy constructors, and a template constructor may be used to copy an object if it provides a better match than other constructors.

Od argumentem szablonu jest const &, jego podpis będzie dokładnie taki sam, jak w sposób dorozumiany-zadeklarowanej funkcji w przypadku kopiowania, więc nigdy nie będzie używany jako konstruktor kopii.

To może być OK, ponieważ w tym przykładzie używasz go jako konstruktora konwersji . Jednak argumenty szablonu przed :: są kontekstem niewydechem, więc kompilator nie może podłączyć A<int>::B i rozwiązać int. Ze względu na różne sposoby specjalizowania szablonów, kompilator nie może ustalić, który z nich kwalifikuje się do tego, który z A. Możesz dodać typedef A<int>::B B; wewnątrz A<float>, a następnie oba int i float będą kwalifikować się jako U.

Można to naprawić, używając SFINAE i dodając typy elementów do klas, aby ułatwić nawigację w hierarchii. Oto demo.

#include <typeinfo> 
#include <iostream> 

template<typename T> 
struct A 
{ 
    typedef T type; 

    struct B 
    { 
     B() {} 

     template<typename U> 
     B(const U& rhs, typename U::nest_A_parent * = NULL) { 
      std::cout << "copied from type " 
      << typeid(typename U::nest_A_parent::type).name() << '\n'; 
     } 

    private: 
     typedef A nest_A_parent; 
     template< typename U > 
     friend struct B; 
    }; 
}; 

int main() 
{ 
    A<int>::B x; 
    A<double>::B y(x); 
} 
2

Jak zrobić konwersja konstruktora (jak Potatoswatter wskazał, że nie może być z definicji konstruktora kopia), które tylko dopasowuje typu zagnieżdżonego B, za każdym A<T>::B:

namespace detail { 

// private type to identify all A<T>::B 
struct B {}; 

} // detail 

// trait to identify all A<T>::B 
// a template alias could also be used here 
template<typename T> 
struct is_b: std::is_base_of<detail::B, T> {}; 

template<typename T> 
struct A 
{ 
    struct B: detail::B { 
     B() {} 

     template<typename U> 
     B(U&& u) 
     { 
      static_assert(is_b<typename std::decay<U>::type>::value 
          , "Conversion only allowed from A<T>::B"); 
     } 
    }; 
}; 

Zaletą tej techniki jest to, że nie używa ona SFINAE, więc niepoprawna próba konwersji zostanie zgłoszona przez static_assert zamiast bezgłośnie. Z drugiej strony potrzebujesz SFINAE, jeśli istnieje co najmniej jeden inny konstruktor konwersji szablonów.

Zakłada się, że A<T>::B będzie różnić się od (dla różnych T i U). Jeśli typy zagnieżdżone są identyczne (jak ma to miejsce w uproszczonym, przykładowym kodzie), lepiej byłoby podać definicję B gdzie indziej i użyć typedef some_private_namespace::B B; w A.

Powiązane problemy