overloading friend operator<< for template class
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.
Related videos on Youtube
starcorn
Updated on July 05, 2022Comments
-
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 over 13 yearsThere was a recent question about this which may be instructive: stackoverflow.com/questions/4571611/virtual-operator
-
starcorn over 13 years@Daniel - it doesn't takes up the problem I have when overloading for a template class
-
David Rodríguez - dribeas over 13 yearsI 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 over 13 yearsYes i already did that, but what about if I don't want the
operator<<
as an inline function? -
starcorn over 13 yearsthanks this works edited my question with this code, I will tick this as answer as soon the ticker goes down.
-
Gene Bushuyev over 13 yearsI 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 andtypename
requires qualified-id. -
John Dibling over 13 years@Gene: hmm. it does compile for me on highest levels with MS extensions turned off.
-
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 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 ofoperator<<
that has the same type. Also, as a side effect of definingoperator<<
inside the class you are limiting the visibility of thatoperator<<
to only those cases where one of the two arguments is aD
--the compiler will not even consider theoperator<<
overload unless one argument isD<T>
. -
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 over 13 yearsIt does not compile with g++, and I trust the compiler in this one. The second argument in the
operator<<
isclass D<typename classT>
, and I think that is incorrect. I would useD<classT>
instead. The keywordclass
is optional there (99.9% of the cases), but the use oftypename
is not one of the two know uses: it would be invalid as a substitute toclass
, and it is to identifying that a dependent name on a template is actually a type. -
David Rodríguez - dribeas over 13 yearsNote 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. 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 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 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 almost 8 yearsImplementing 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 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 over 6 yearsThis 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 over 5 yearsIn 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 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 onlyoperator <<
friended to that is theoperator << <T>
instantiation. Some arbitraryTest<U>
, whereU
is not synonymous withT
, does not friendoperator << <T>
; it friendsoperator << <U>
instead. It may not seem a big deal, but it can quietly become one without realizing it. -
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 about 4 yearsIn the third example, what's the purpose of the template declaration inside the class? Isn't the first forward declaration enough?
-
Evgeny over 3 years@DavidRodríguez-dribeas, what can I do with first idea for introverts, if code implementation not in header file?
-
ulatekh over 2 yearsYou can ensure that
classT
andSclassT
are the same withstd::enable_if
andstd::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);