2011-01-11 10 views
36

He leído un par de preguntas sobre mi problema en stackoverflow ahora, y nada parece resolver mi problema. O tal vez lo he hecho mal ... Sobrecargado << si lo hago en una función en línea. ¿Pero cómo hago que funcione en mi caso?sobrecargando el operador amigo << para la clase de plantilla

warning: friend declaration std::ostream& operator<<(std::ostream&, const D<classT>&)' declares a non-template function

warning: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here) -Wno-non-template-friend disables this warning

/tmp/cc6VTWdv.o:uppgift4.cc:(.text+0x180): undefined reference to operator<<(std::basic_ostream<char, std::char_traits<char> >&, D<int> const&)' collect2: ld returned 1 exit status

template <class T> 
T my_max(T a, T b) 
{ 
    if(a > b)  
     return a; 
    else 
     return b; 
} 

template <class classT> 
class D 
{ 
public: 
    D(classT in) 
     : d(in) {}; 
    bool operator>(const D& rhs) const; 
    classT operator=(const D<classT>& rhs); 

    friend ostream& operator<< (ostream & os, const D<classT>& rhs); 
private: 
    classT d; 
}; 


int main() 
{ 

    int i1 = 1; 
    int i2 = 2; 
    D<int> d1(i1); 
    D<int> d2(i2); 

    cout << my_max(d1,d2) << endl; 
    return 0; 
} 

template <class classT> 
ostream& operator<<(ostream &os, const D<classT>& rhs) 
{ 
    os << rhs.d; 
    return os; 
} 
+0

Hubo una pregunta acerca de este reciente que puede ser instructivo: http://stackoverflow.com/questions/4571611/virtual-operator/ –

+0

@Daniel - no soluciona el problema que tengo cuando sobrecarga para una clase de plantilla – starcorn

+5

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. –

Respuesta

100

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... 
    } 
} 
+5

mucho más claro que la respuesta seleccionada – TheInvisibleFist

+0

La implementación de la función fuera de la clase proporciona una referencia indefinida. Dentro de la clase me sale un problema tan pronto como instalo explícitamente la clase de host con más de un tipo. – dgrat

+0

@dgrat: No puedo depurar código que no estoy viendo, pero funciona. ¿El amigo depende de alguno de los argumentos de la plantilla? Si no es así, obtendría problemas con múltiples definiciones, ya que las diferentes instancias generarían exactamente la función * same * (o una violación de ODR si la firma de la función es la misma, pero el cuerpo es diferente). –

11

No se puede declarar un amigo así, es necesario que especifique un tipo de plantilla diferente para ella.

template <typename SclassT> 
friend ostream& operator<< (ostream & os, const D<SclassT>& rhs); 

nota SclassT por lo que no classT sombra. Al definir

template <typename SclassT> 
ostream& operator<< (ostream & os, const D<SclassT>& rhs) 
{ 
    // body.. 
} 
+0

gracias a que este trabajo editó mi pregunta con este código, marcaré esto como respuesta tan pronto como el indicador deje de funcionar. – starcorn

+4

Tenga en cuenta que esto no declara una instanciación particular del 'operador <<' como un amigo, sino más bien ** todas ** instancias, incluida cualquier especialización de la plantilla. Vea la respuesta [aquí] (http://stackoverflow.com/questions/4660123/overloading-friend-operator-for-template-class/4661372#4661372) –

+0

@starcorn debe cambiar la respuesta seleccionada para proporcionar una mejor respuesta y esa debería ser la respuesta de David Rodríguez. –

2

Esto funcionó para mí sin ninguna advertencia del compilador.

#include <iostream> 
using namespace std; 

template <class T> 
T my_max(T a, T b) 
{ 
    if(a > b) 
    return a; 
    else 
    return b; 
} 

template <class classT> 
class D 
{ 
public: 
    D(classT in) 
    : d(in) {}; 

    bool operator>(const D& rhs) const { 
    return (d > rhs.d); 
    } 

    classT operator=(const D<classT>& rhs); 

    friend ostream& operator<< (ostream & os, const D& rhs) { 
    os << rhs.d; 
    return os; 
    } 

private: 
    classT d; 
}; 


int main() 
{ 

    int i1 = 1; 
    int i2 = 2; 
    D<int> d1(i1); 
    D<int> d2(i2); 

    cout << my_max(d1,d2) << endl; 
    return 0; 
} 
+0

Sí, ya lo hice, pero qué sobre si no quiero el 'operador <<' como una función en línea? – starcorn

+0

@starcorn: Cuando un método/función se adapta en línea (implícita o explícitamente) tiene poco que ver con el tiempo, la función está realmente inscrita en el código. Por lo tanto, esta es una preocupación sin sentido. –

+0

+1. @starcorn: esta solución es mejor que la aceptada. La diferencia es sutil, pero en la respuesta aceptada, declaras todas las instancias (y posibles especializaciones) del 'operador <<' como amigos, mientras que en esta solución solo estás otorgando acceso a la instanciación de 'operator <<' que tiene el mismo tipo. Además, como efecto secundario de definir 'operator <<' dentro de la clase, estás limitando la visibilidad de ese 'operator <<' a solo aquellos casos en los que uno de los dos argumentos es 'D' - el compilador no considere la sobrecarga 'operator <<' a menos que un argumento sea 'D '. –

0

Aquí van:

#include <cstdlib> 
#include <iostream> 
using namespace std; 

template <class T> 
T my_max(T a, T b) 
{ 
    if(a > b)  
     return a; 
    else 
     return b; 
} 

template <class classT> 
class D 
{ 
public: 
    D(classT in) 
     : d(in) {}; 
    bool operator>(const D& rhs) const { return d > rhs.d;}; 
    classT operator=(const D<classT>& rhs); 

    template<class classT> friend ostream& operator<< (ostream & os, const D<classT>& rhs); 
private: 
    classT d; 
}; 

template<class classT> ostream& operator<<(ostream& os, class D<typename classT> const& rhs) 
{ 
    os << rhs.d; 
    return os; 
} 


int main() 
{ 

    int i1 = 1; 
    int i2 = 2; 
    D<int> d1(i1); 
    D<int> d2(i2); 

    cout << my_max(d1,d2) << endl; 
    return 0; 
} 
+0

No creo que esto deba compilarse: 'template ostream & operator << (ostream & os, clase D const & rhs)'. El tipo elaborado no está permitido en la declaración de parámetros y 'typename' requiere id calificado. –

+0

@Gene: hmm. compila para mí en los niveles más altos con las extensiones MS desactivadas. –

+0

No compila con g ++, y confío en el compilador de este. El segundo argumento en 'operator <<' es 'class D ', y creo que es incorrecto. Yo usaría 'D ' en su lugar. La palabra clave 'class' es opcional allí (99.9% de los casos), pero el uso de' typename' no es uno de los dos usos conocidos: sería inválido como sustituto de 'clase', y es para identificarlo un nombre dependiente en una plantilla es en realidad un tipo. –

0

creo que no se debe hacer al amigo en el primer lugar.

Se puede crear una impresión llamada a un método público, algo como esto (para una clase no plantilla):

std::ostream& MyClass::print(std::ostream& os) const 
{ 
    os << "Private One" << privateOne_ << endl; 
    os << "Private Two" << privateTwo_ << endl; 
    os.flush(); 
    return os; 
} 

y luego, fuera de la clase (pero en el mismo espacio de nombres)

std::ostream& operator<<(std::ostream& os, const MyClass& myClass) 
{ 
    return myClass.print(os); 
} 

Creo que debería funcionar también para la clase de plantilla, pero aún no lo he probado.

Cuestiones relacionadas