overloading friend operator << per template class

Ho letto un paio di domande sul mio problema su StackOverflow ora, e nessuna sembra risolvere il mio problema. O forse l’ho fatto male … Il sovraccarico << se lo faccio in una funzione inline. Ma come faccio a farlo funzionare nel mio caso?

warning: friend declaration std::ostream& operator<<(std::ostream&, const D&)' 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 >&, D const&)' collect2: ld returned 1 exit status

Il codice:

 template  T my_max(T a, T b) { if(a > b) return a; else return b; } template  class D { public: D(classT in) : d(in) {}; bool operator>(const D& rhs) const; classT operator=(const D& rhs); friend ostream& operator<< (ostream & os, const D& rhs); private: classT d; }; int main() { int i1 = 1; int i2 = 2; D d1(i1); D d2(i2); cout << my_max(d1,d2) << endl; return 0; } template  ostream& operator<<(ostream &os, const D& rhs) { os << rhs.d; return os; } 

Questa è una di quelle domande frequenti che hanno approcci diversi che sono simili ma non proprio gli stessi. I tre approcci differiscono in chi dichiari di essere un amico della tua funzione – e poi su come lo realizzi.

L’estroverso

Dichiara tutte le istanze del modello come amici. Questo è ciò che hai accettato come risposta, e anche ciò che la maggior parte delle altre risposte propone. In questo approccio stai aprendo inutilmente la tua particolare istanziazione D dichiarando agli amici tutte le istanze operator<< . Cioè, std::ostream& operator<<( std::ostream &, const D& ) ha accesso a tutti gli interni di D .

 template  class Test { template  // all instantiations of this template are my friends friend std::ostream& operator<<( std::ostream&, const Test& ); }; template  std::ostream& operator<<( std::ostream& o, const Test& ) { // Can access all Test, Test... regardless of what T is } 

Gli introversi

Dichiara solo un'istanza particolare dell'operatore di inserimento come amico. D può piacere l'operatore di inserimento quando viene applicato a se stesso, ma non vuole nulla a che fare con std::ostream& operator<<( std::ostream&, const D& ) .

Questo può essere fatto in due modi: il modo semplice di essere proposto da Emery Berger, che è la definizione dell'operatore, che è anche una buona idea per altri motivi:

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

In questa prima versione, non si sta creando un operator<< modello operator<< , ma piuttosto una funzione non basata su un modello per ogni istanza del modello di Test . Ancora una volta, la differenza è sottile ma fondamentalmente equivale all'aggiunta manuale: std::ostream& operator<<( std::ostream&, const Test& ) quando si istanzia Test e un altro sovraccarico simile quando si crea un'istanza Test con double o con qualsiasi altro tipo.

La terza versione è più ingombrante. Senza inline il codice e con l'uso di un modello, puoi dichiarare un'istanza singola del modello come amico della tua class, senza aprirti a tutte le altre istanze:

 // Forward declare both templates: template  class Test; template  std::ostream& operator<<( std::ostream&, const Test& ); // Declare the actual templates: template  class Test { friend std::ostream& operator<< ( std::ostream&, const Test& ); }; // Implement the operator template  std::ostream& operator<<( std::ostream& o, const Test& t ) { // Can only access Test for the same T as is instantiating, that is: // if T is int, this template cannot access Test, Test ... } 

Approfittando dell'estroverso

La sottile differenza tra questa terza opzione e la prima è in quanto stai aprendo ad altre classi. Un esempio di abuso nella versione estroversa sarebbe qualcuno che vuole ottenere l'accesso al tuo interno e lo fa:

 namespace hacker { struct unique {}; // Create a new unique type to avoid breaking ODR template <> std::ostream& operator<< ( std::ostream&, const Test& ) { // if Test is an extrovert, I can access and modify *any* Test!!! // if Test is an introvert, then I can only mess up with Test // which is just not so much fun... } } 

Non è ansible dichiarare un amico come quello, è necessario specificare un tipo di modello diverso per esso.

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

nota SclassT modo che non ombreggia la classT . Quando si definisce

 template  ostream& operator<< (ostream & os, const D& rhs) { // body.. } 

Questo ha funzionato per me senza alcun avvertimento del compilatore.

 #include  using namespace std; template  T my_max(T a, T b) { if(a > b) return a; else return b; } template  class D { public: D(classT in) : d(in) {}; bool operator>(const D& rhs) const { return (d > rhs.d); } classT operator=(const D& 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 d1(i1); D d2(i2); cout << my_max(d1,d2) << endl; return 0; } 

Ecco qui:

 #include  #include  using namespace std; template  T my_max(T a, T b) { if(a > b) return a; else return b; } template  class D { public: D(classT in) : d(in) {}; bool operator>(const D& rhs) const { return d > rhs.d;}; classT operator=(const D& rhs); template friend ostream& operator<< (ostream & os, const D& rhs); private: classT d; }; template ostream& operator<<(ostream& os, class D const& rhs) { os << rhs.d; return os; } int main() { int i1 = 1; int i2 = 2; D d1(i1); D d2(i2); cout << my_max(d1,d2) << endl; return 0; } 

Penso che non dovresti diventare amico in primo luogo.

È ansible creare una stampa di chiamata del metodo pubblico, qualcosa di simile a questo (per una class non template):

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

e quindi, fuori dalla class (ma nello stesso spazio dei nomi)

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

Penso che dovrebbe funzionare anche per template class, ma non ho ancora testato.