2011-07-04 25 views
15

Ésta compila y funciona como debería (plantilla no anidada):operador << (ostream Y, X) para la clase X anidado en una plantilla de clase

#include <iostream> 

template<typename T> class Z; 

template <typename T> 
std::ostream& operator<< (std::ostream& os, const Z<T>&) { 
    return (os << "Z"); 
} 

template<typename T> class Z { 
    friend std::ostream& operator<< <> (std::ostream& os, const Z&); 
}; 

int main() { 
    Z<int> z; 
    std::cout << z << std::endl; 
} 

Éste no compilar (gcc 4.4 y gcc 4.6, tanto en el modo de 03 y 0x):

#include <iostream> 

template<typename T> class Z; 

template<typename T> 
std::ostream& operator<< (std::ostream& os, const typename Z<T>::ZZ&) { 
    return (os << "ZZ!"); 
} 

template <typename T> class Z { 
    public: 
    class ZZ { 
     friend std::ostream& operator<< <> (std::ostream& os, const ZZ&); 
    }; 
}; 


int main() { 
    Z<int>::ZZ zz; 
    std::cout << zz << std::endl; 
} 

el mensaje de error es similar a esto:

error: template-id ‘operator<< <>’ for ‘std::ostream& operator<<(std::ostream&, 
const Z<int>::ZZ&)’ does not match any template declaration 
error: no match for ‘operator<<’ in ‘std::cout << zz’ 

en el modo 0x el segundo error que me El sage es diferente, pero el significado es el mismo.

¿Es posible hacer lo que quiero hacer?

EDITAR Al parecer, hay una instancia de contexto no deducido aquí, lo que explica los mensajes de error. La pregunta, sin embargo, sigue en pie: ¿puedo tener un operator<< que funcione para una clase anidada en una plantilla de clase?

+0

Estoy trabajando en ello ... El primer error se soluciona al poner "operator << ", que también hace las cosas mucho más legibles. – AudioDroid

+0

posible duplicado de [¿Cómo deducir el tipo de clase del tipo de método en las plantillas C++?] (Http://stackoverflow.com/questions/3830491/how-touce-class-type-type-from-method-type-in-c -templates) –

+0

@Matthieu: Me gustaría ver el código de ese enlace adaptado a este problema. ¿Podría poner eso como una respuesta para los "mortales ordinarios" como yo (y posiblemente "n.m.") aquí? ;-) – AudioDroid

Respuesta

3

Matthieu explica muy bien el problema, sino una obra sencilla -alrededor se puede usar en este caso. se puede implementar el amigo función dentro de la clase con la declaración amigo:

#include <iostream> 

template <typename T> class Z { 
public: 
    class ZZ { 
    friend std::ostream& operator<< (std::ostream& os, const ZZ&) { 
     return os << "ZZ!"; 
    } 
    }; 
}; 


int main() { 
    Z<int>::ZZ zz; 
    std::cout << zz << std::endl; 
} 
+0

gracias! Usted acaba de hacer mi día. – niklasfi

4

Aparte del problema que la declaración amigo no coincide con la plantilla del operador (tal vez como corregible)

class ZZ { 
    template<class U> 
    friend std::ostream& operator<<(std::ostream& os, const ZZ&); 
}; 

también tiene un problema con un "contexto no deducido-", que es lo que los enlaces Matthieu a.

En esta plantilla

template<typename T> 
std::ostream& operator<< (std::ostream& os, const typename Z<T>::ZZ&) { 
    return (os << "ZZ!"); 
} 

el compilador no es capaz de averiguar por qué coincidirá de T que parámetro. Podría haber varias coincidencias, si se especializan para algunos tipos

template<> 
class Z<long> 
{ 
public: 
    typedef double ZZ; 
}; 

template<> 
class Z<bool> 
{ 
public: 
    typedef double ZZ; 
}; 

Ahora bien, si se intenta imprimir un double, T podría ser bool o long.

El compilador no puede saber esto con certeza sin verificar todos posibles T, y no tiene que hacer eso. Simplemente salta a su operador en su lugar.

+0

¿Existe alguna forma de evitar este problema? Puedo definir 'operator <<' inline right en la declaración de amigo, y todo parece funcionar. Pero si no me gusta una definición en línea por alguna razón, ¿hay alguna otra manera? –

+0

@ n.m. - No creo que haya una mejor manera. Si define el operador dentro de cada clase, evitará el problema de mecanizar cualquier especialización posible. Las plantillas son solo encabezadas con funciones en línea, nos guste o no. :-( –

7

Este es un problema general para las funciones:

template <typename C> 
void func(typename C::iterator i); 

Ahora, si llamo func(int*), cuyo valor de C debo usar?

En general, no puede trabajar hacia atrás! Muchos C diferentes podrían haber definido un tipo interno iterator que resulta ser int* para algún conjunto de parámetros.

En su caso, usted está complicando un poco la situación:

template <typename T> 
void func(typename Z<T>::ZZ const&); 

Pero fundamentalmente Este es el mismo problema, Z<T> es una plantilla, no una clase completa, y que están pidiendo para crear una función para el tipo interno ZZ de esta plantilla.

supongo que sí:

template <typename T> 
struct Z { typedef T ZZ; }; 

template <typename T> 
struct Z<T const> { typedef T ZZ; }; 

Nota: típico de iteradores, la value_type no es const cualificado

Entonces, cuando se invoca func(int), debería usar Z<int> o Z<int const>?

No es deducible.

Y así todo se conoce como contexto no deducible, y el estándar lo prohíbe porque no hay una respuesta sensata.

regla de oro: sea sospechosa de typename en los parámetros de una función.

Nota: son bien si otro argumento ya se inmovilizó el tipo, ejemplo typename C::iterator find(C&, typename C::const_reference); porque una vez C se deduce, entonces C::const_reference se puede utilizar sin problemas

+1

Entonces, ¿hay alguna solución? Quiero 'operator <<' para mi clase anidada, ¿hay alguna manera de definirlo? Excepto definirla en línea directamente en la declaración de amigo. –

+1

@nm: no, no hay, mientras la clase permanezca anidada.Una opción sería definir la clase fuera de la plantilla 'Z' (en un espacio de nombres' details' por ejemplo) junto con su 'operator <<' y luego dentro de 'Z' usar un' typedef'. –

+0

@Matthieu una opción sería confiar en CRTP. Ver mi respuesta para una explicación. No es tan bueno como el modo amigo, pero si uno no puede usarlo, uno podría intentarlo :) –

2

Una técnica diferente es el de proporcionar un fuera de línea de plantilla de clase que funciona como un gancho.

template <typename T> class ZZZ { 
    T &getDerived() { return static_cast<T&>(*this); } 
    T const &getDerived() const { return static_cast<T const&>(*this); } 

protected: 
    ~ZZZ() { } 
}; 

template <typename T> class Z { 
    public: 
    class ZZ : public ZZZ<ZZ> { 
    }; 
}; 

template<typename ZZ> 
std::ostream& operator<< (std::ostream& os, const ZZZ<ZZ> &zzz) { 
    ZZ const& zz = zzz.getDerived(); 
    /* now you can use zz */ 
    return (os << "ZZ!"); 
} 

Sin embargo, es preferible utilizar una definición de función amiga. Pero es bueno saber sobre alternativas.

+0

Buen truco, mucho más genérico y deja de lado la filtración del alcance que tuve :) –

+0

Gracias. ¡Un buen truco! –

Cuestiones relacionadas