Esta es una de esas preguntas frecuentes que tienen diferentes enfoques que son similares pero no son lo mismo. Los tres enfoques difieren en quién declara ser un amigo de su función, y luego en cómo lo implementa.
El extrovertido
declarar todas las instancias de la plantilla como amigos. Esto es lo que ha aceptado como respuesta, y también lo que la mayoría de las otras respuestas proponen. En este enfoque, usted está innecesariamente abriendo su instanciación particular D<T>
al declarar amigos todas las instancias operator<<
. Es decir, std::ostream& operator<<(std::ostream &, const D<int>&)
tiene acceso a todas las partes internas de D<double>
.
template <typename T>
class Test {
template <typename U> // all instantiations of this template are my friends
friend std::ostream& operator<<(std::ostream&, const Test<U>&);
};
template <typename T>
std::ostream& operator<<(std::ostream& o, const Test<T>&) {
// Can access all Test<int>, Test<double>... regardless of what T is
}
Los introvertidos
declaran Sólo una instanciación particular del operador de inserción como un amigo. D<int>
puede gustarle el operador de inserción cuando se aplica a sí mismo, pero no desea tener nada que ver con std::ostream& operator<<(std::ostream&, const D<double>&)
.
Esto se puede hacer de dos maneras, la sencilla manera de ser propuesto @Emery Berger, que es el operador de procesos en línea -que es también una buena idea por otras razones:
template <typename T>
class Test {
friend std::ostream& operator<<(std::ostream& o, const Test& t) {
// can access the enclosing Test. If T is int, it cannot access Test<double>
}
};
En esta primera versión , usted es no creando una plantilla operator<<
, sino más bien una función sin plantilla para cada instanciación de la plantilla Test
. Nuevamente, la diferencia es sutil, pero esto es básicamente equivalente a agregar manualmente: std::ostream& operator<<(std::ostream&, const Test<int>&)
al instanciar Test<int>
, y otra sobrecarga similar al crear instancia Test
con , o con cualquier otro tipo.
La tercera versión es más engorrosa. Sin inlining el código, y con el uso de una plantilla, se puede declarar una única instancia de la plantilla de un amigo de su clase, sin abrirse a todos los demás ejemplificaciones:
// Forward declare both templates:
template <typename T> class Test;
template <typename T> std::ostream& operator<<(std::ostream&, const Test<T>&);
// Declare the actual templates:
template <typename T>
class Test {
friend std::ostream& operator<< <T>(std::ostream&, const Test<T>&);
};
// Implement the operator
template <typename T>
std::ostream& operator<<(std::ostream& o, const Test<T>& t) {
// Can only access Test<T> for the same T as is instantiating, that is:
// if T is int, this template cannot access Test<double>, Test<char> ...
}
Aprovechando la extrovertido
La diferencia sutil entre esta tercera opción y la primera es en cuánto está abriendo a otras clases. Un ejemplo de abuso en la versión extrovertida sería alguien que quiera acceder a sus componentes internos y hace esto:
namespace hacker {
struct unique {}; // Create a new unique type to avoid breaking ODR
template <>
std::ostream& operator<< <unique>(std::ostream&, const Test<unique>&)
{
// if Test<T> is an extrovert, I can access and modify *any* Test<T>!!!
// if Test<T> is an introvert, then I can only mess up with Test<unique>
// which is just not so much fun...
}
}
Hubo una pregunta acerca de este reciente que puede ser instructivo: http://stackoverflow.com/questions/4571611/virtual-operator/ –
@Daniel - no soluciona el problema que tengo cuando sobrecarga para una clase de plantilla – starcorn
Creo que es mejor si no modificas la pregunta con una respuesta dada. Hace que sea más difícil determinar cuál fue el problema original. Es posible que desee agregar un ** EDIT ** al final con el cambio que * resolvió * el problema, pero me resulta confuso cuando las preguntas cambian horas extras y tengo que sacar el historial para ver qué se estaba preguntando realmente en el primer lugar. –