overloading friend operator<< for template class

55,151

Solution 1

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

Solution 2

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

Solution 3

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

Solution 4

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.

Share:
55,151

Related videos on Youtube

starcorn
Author by

starcorn

Updated on July 05, 2022

Comments

  • starcorn
    starcorn almost 2 years

    I have read couple of the questions regarding my problem on StackOverflow.com now, and none of it seems to solve my problem. Or I maybe have done it wrong... The overloaded << works if I make it into an inline function. But how do I make it work in my case?

    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

    The code:

    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;
    }
    
    • Daniel Trebbien
      Daniel Trebbien over 13 years
      There was a recent question about this which may be instructive: stackoverflow.com/questions/4571611/virtual-operator
    • starcorn
      starcorn over 13 years
      @Daniel - it doesn't takes up the problem I have when overloading for a template class
    • David Rodríguez - dribeas
      David Rodríguez - dribeas over 13 years
      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.
  • starcorn
    starcorn over 13 years
    Yes i already did that, but what about if I don't want the operator<< as an inline function?
  • starcorn
    starcorn over 13 years
    thanks this works edited my question with this code, I will tick this as answer as soon the ticker goes down.
  • Gene Bushuyev
    Gene Bushuyev over 13 years
    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.
  • John Dibling
    John Dibling over 13 years
    @Gene: hmm. it does compile for me on highest levels with MS extensions turned off.
  • Martin York
    Martin York over 13 years
    @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.
  • David Rodríguez - dribeas
    David Rodríguez - dribeas over 13 years
    +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>.
  • David Rodríguez - dribeas
    David Rodríguez - dribeas over 13 years
    @starcorn: I have added an answer that tries to clear the differences in three different approaches here
  • David Rodríguez - dribeas
    David Rodríguez - dribeas over 13 years
    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.
  • David Rodríguez - dribeas
    David Rodríguez - dribeas over 13 years
    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 here
  • Rupesh Yadav.
    Rupesh Yadav. about 8 years
    @starcorn you should change your selected answer in order to provide better answer and that should be David Rodriguez's answer.
  • starcorn
    starcorn about 8 years
    @Nim based on user opinion I changed the selected answer for my question. However thanks for your contribution to help me at that time :)
  • Nim
    Nim about 8 years
    @starcorn, no worries, I voted for David's answer as well way back then, it's a more comprehensive treatment of the problem..
  • dgrat
    dgrat almost 8 years
    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.
  • David Rodríguez - dribeas
    David Rodríguez - dribeas almost 8 years
    @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).
  • WhozCraig
    WhozCraig over 6 years
    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.
  • wangdq
    wangdq over 5 years
    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 ?
  • WhozCraig
    WhozCraig over 5 years
    @wangdq 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.
  • Admin
    Admin over 5 years
    @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.
  • Clément
    Clément about 4 years
    In the third example, what's the purpose of the template declaration inside the class? Isn't the first forward declaration enough?
  • Evgeny
    Evgeny over 3 years
    @DavidRodríguez-dribeas, what can I do with first idea for introverts, if code implementation not in header file?
  • ulatekh
    ulatekh over 2 years
    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);