2011-09-11 13 views
5

Me gustaría especificar una conversión de A<T>::B a A<U>::B.constructor de copia generalizada en una clase interna

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

pensé que esto lo haría, pero me sale el error del compilador:

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

¿Por qué no es mi código correcto, y lo que es la forma correcta para lograr la conversión deseada?

+0

Para aclarar, ¿cuál es la conversión deseada? Porque no está convirtiendo un int en un doble, sino un A :: B en un A :: B, que es una clase de otra clase. – SSight3

+0

@ SSight3: déjame adivinar que Robert está intentando volver a implementar punteros inteligentes con recuentos externos y asignaciones polimórficas ... – sehe

+0

La conversión es de A :: B a A :: B para cualquier T, U. int y double son solo los ejemplos que utilicé. Y no, sehe, eso no es lo que estoy haciendo. –

Respuesta

3

modificado ligeramente fuente, para demostrar que se trata de los límites para el sistema de tipo de deducción:

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 
} 

Ves que cuando ayudamos a que el compilador un poco, ya no hay problema. También tenga en cuenta el error de compilador real (gcc) muestra que es confusión:

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] 

Aviso la parte donde U = U (es decir, sin resolver)

4

Una plantilla no puede ser un constructor de copia. §12.8/2, nota:

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.

Dado que el argumento de la plantilla es const &, su firma será exactamente la misma que la función declarada implícitamente en el caso de copia, por lo que nunca será utilizado como un constructor de copia.

Eso puede estar bien, porque en el ejemplo lo está usando como constructor de conversión. Sin embargo, los argumentos de plantilla antes de :: son un contexto no deducido, por lo que el compilador no puede conectar A<int>::B y resolver int. Debido a las diversas formas de especialización de plantillas, el compilador no tiene forma de averiguar qué A, si corresponde, califica. Podría agregar typedef A<int>::B B; dentro de A<float>, y luego int y float calificaría como U.

Puede solucionar esto utilizando SFINAE y agregando tipos de miembro a las clases para ayudar a navegar por la jerarquía. Aquí es un 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

Cómo hacer un constructor conversión (como Potatoswatter señaló, no puede ser un constructor de copia por definición) que sólo coincide con el tipo anidado B, para cualquier 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"); 
     } 
    }; 
}; 

Esta técnica tiene la ventaja de que no utiliza SFINAE por lo que se informará un intento de conversión no válido a través de static_assert en lugar de fallar silenciosamente. Por otro lado, necesitará SFINAE si hay al menos otro constructor de conversión de plantilla.

Esto está asumiendo que A<T>::B diferirá de (para diferentes T y U). Si los tipos anidados son idénticos (como es el caso en su código de ejemplo simplificado), sería mejor que definiera B en otro lugar y usando typedef some_private_namespace::B B; en A.

Cuestiones relacionadas