Overloading friend operator<< for class template
Asked Answered
G

5

87

The overloaded << works if I make it into an inline function. But how do I make it work in my case?

#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;
   classT operator=(const D<classT>& rhs);

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

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

int main() {
   D<int> d1(1);
   D<int> d2(2);
   cout << my_max(d1, d2) << endl;
}

This fails to compile:

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

Grayback answered 11/1, 2011 at 16:45 Comment(3)
There was a recent question about this which may be instructive: https://mcmap.net/q/243222/-making-operator-lt-lt-virtualKp
@Daniel - it doesn't takes up the problem I have when overloading for a template classGrayback
I think it is better if you do not modify the question with a given answer. It makes it harder to determine what the original problem was. You might want to add an EDIT at the end with the change that solved the issue, but I find it confusing when questions change overtime and I have to pull up the history to see what was actually being asked in the first place.Kizzie
A
213

This is one of those frequently asked questions that have different approaches that are similar but not really the same. The three approaches differ in who you are declaring to be a friend of your function --and then on how you implement it.

The extrovert

Declare all instantiations of the template as friends. This is what you have accepted as answer, and also what most of the other answers propose. In this approach you are needlessly opening your particular instantiation D<T> by declaring friends all operator<< instantiations. That is, std::ostream& operator<<( std::ostream &, const D<int>& ) has access to all internals of 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
}

The introverts

Only declare a particular instantiation of the insertion operator as a friend. D<int> may like the insertion operator when applied to itself, but it does not want anything to do with std::ostream& operator<<( std::ostream&, const D<double>& ).

This can be done in two ways, the simple way being as @Emery Berger proposed, which is inlining the operator --which is also a good idea for other reasons:

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

In this first version, you are not creating a templated operator<<, but rather a non-templated function for each instantiation of the Test template. Again, the difference is subtle but this is basically equivalent to manually adding: std::ostream& operator<<( std::ostream&, const Test<int>& ) when you instantiate Test<int>, and another similar overload when you instantiate Test with double, or with any other type.

The third version is more cumbersome. Without inlining the code, and with the use of a template, you can declare a single instantiation of the template a friend of your class, without opening yourself to all other instantiations:

// 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> ...
}

Taking advantage of the extrovert

The subtle difference between this third option and the first is in how much you are opening to other classes. An example of abuse in the extrovert version would be someone that wants to get access into your internals and does this:

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...
   }
}
Austerlitz answered 11/1, 2011 at 18:45 Comment(8)
Implementing the function outside of the class gives a undefined reference. Inside of the class I get a problem as soon as I explicitely instanciate the host class with more than one type.Sexist
@dgrat: I cannot debug code that I am not seeing, but it does work. Is the friend dependent on any of the template arguments? If it is not, you would get issues with multiple definitions as different instantiations would generate exactly the same function (or an ODR violation if the function signature is the same but the body differs).Kizzie
This continues to be one of my more referred go-to's for people seeking to friend template functions (not just operators) due in no small part to the distinct differences in ways i can be done, and the benefits/detractions for each. Just a stellar answer, David. I'd uptick it a dozen times over if I could.Lands
In third version is "operator<< <T>" this line means instantiate the function template of operator << as a friend of class template Test with the same T ?Pennypennyaliner
@Pennypennyaliner Yes, and that specialization is friended to, and only to, that friended-by template specialization. I.e. If you have some Test<T> the only operator << friended to that is the operator << <T> instantiation. Some arbitrary Test<U>, where U is not synonymous with T, does not friend operator << <T>; it friends operator << <U> instead. It may not seem a big deal, but it can quietly become one without realizing it.Lands
@DavidRodríguez-dribeas well studied, but I am not entirely clear how I would solve the following in the "Introvert" style : wandbox.org/permlink/jMKpn6CKFL2cyceO ... It seems if I like to preserve the full encapsulation of nested type names I can not use the simple operator overloading on them.Benghazi
In the third example, what's the purpose of the template declaration inside the class? Isn't the first forward declaration enough?Publias
@DavidRodríguez-dribeas, what can I do with first idea for introverts, if code implementation not in header file?Shortridge
V
16

You can't declare a friend like that, you need to specify a different template type for it.

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

note SclassT so that it doesn't shadow classT. When defining

template <typename SclassT>
ostream& operator<< (ostream & os, const D<SclassT>& rhs)
{
  // body..
}
Valgus answered 11/1, 2011 at 16:49 Comment(6)
thanks this works edited my question with this code, I will tick this as answer as soon the ticker goes down.Grayback
Note that this is not declaring a particular instantiation of the operator<< as a friend, but rather all instantiations, including any specialization of the template. See the answer hereKizzie
@Grayback you should change your selected answer in order to provide better answer and that should be David Rodriguez's answer.Janiculum
@Valgus based on user opinion I changed the selected answer for my question. However thanks for your contribution to help me at that time :)Grayback
@starcorn, no worries, I voted for David's answer as well way back then, it's a more comprehensive treatment of the problem..Valgus
You can ensure that classT and SclassT are the same with std::enable_if and std::is_same on any of the types in the declaration, e.g. template <typename SclassT> typename std::enable_if<std::is_same<classT,SclassT>::value, ostream>::type& operator<< (ostream & os, const D<SclassT>& rhs);Papeete
C
4

This worked for me without any compiler warnings.

#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;
}
Comte answered 11/1, 2011 at 16:54 Comment(4)
Yes i already did that, but what about if I don't want the operator<< as an inline function?Grayback
@starcorn: Wheather a method/function is taged inline (implicitly or explicitly) has little to do weather the function is actually inlined in the code. Therefore this is a meaningless worry.Abduct
+1. @starcorn: This solution is better than the accepted one. The differce is subtle, but in the accepted answer, you are declaring all instantiations (and possible specializations) of the operator<< as friends, while in this solution you are only granting access to the instantiation of operator<< that has the same type. Also, as a side effect of defining operator<< inside the class you are limiting the visibility of that operator<< to only those cases where one of the two arguments is a D --the compiler will not even consider the operator<< overload unless one argument is D<T>.Kizzie
@starcorn: I have added an answer that tries to clear the differences in three different approaches hereKizzie
S
1

I think you shouldn't make friend in the first place.

You can create a public method call print, something like this (for a non template class):

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

and then, outside the class (but in the same namespace)

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

I think it should work also for template class, but I haven't tested yet.

Shiau answered 11/1, 2011 at 17:47 Comment(0)
A
0

Here you go:

#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;
}
Amalberga answered 11/1, 2011 at 17:2 Comment(3)
I don't think this should compile: template<class classT> ostream& operator<<(ostream& os, class D<typename classT> const& rhs). Elaborated type is not allowed in parameter declaration and typename requires qualified-id.Vaudois
@Gene: hmm. it does compile for me on highest levels with MS extensions turned off.Amalberga
It does not compile with g++, and I trust the compiler in this one. The second argument in the operator<< is class D<typename classT>, and I think that is incorrect. I would use D<classT> instead. The keyword class is optional there (99.9% of the cases), but the use of typename is not one of the two know uses: it would be invalid as a substitute to class, and it is to identifying that a dependent name on a template is actually a type.Kizzie

© 2022 - 2024 — McMap. All rights reserved.