Reputation: 53
I'm implementing a dictionary in C++ using a binary tree, each node in my tree has a key(int
), item(string
) and left and right child.
During this implementation, I overloaded the ostream operator for my BinaryTree
class to print out the contents of the tree.
Additionally, I overloaded the ostream to work with Node
pointers that would then print out the key and the item of that node.
This worked fine. However when I tried to then make the tree a template to work with any type for my key or item overloading these operators became more difficult.
I isolated the problem to make it easier to work on, additionally, I have tried playing around with both a node and a node pointer to see if I can get one to work without the other.
Here is the class I have made to test the problem, this class is not templated and works fine.
test.h
class myClass
{
public:
using Key = int;
myClass(Key);
friend std::ostream & operator<<(std::ostream &, const myClass &);
private:
struct Thing {
Key data;
Thing();
Thing(Key);
};
Thing* B;
Thing A;
void disp(std::ostream &) const;
friend std::ostream & operator<<(std::ostream &, myClass::Thing);
friend std::ostream & operator<<(std::ostream &, myClass::Thing *);
};
test.cpp
myClass::Thing::Thing(Key Num) { data = Num; }
myClass::myClass(Key Num)
{
A = Thing(Num); B = &A;
}
void myClass::disp(std::ostream & os) const
{
os << A << std::endl; os << B << std::endl;
}
std::ostream & operator<<(std::ostream & os, const myClass & c)
{
c.disp(os); return os;
}
std::ostream & operator<<(std::ostream & os, myClass::Thing th)
{
os << th.data; return os;
}
std::ostream & operator<<(std::ostream & os, myClass::Thing *th)
{
os << th->data; return os;
}
With this class, I can easily make an instance of my class and std::cout
it giving the output as expected.
Then turning this class into a template:
template <class T> class myTemplate
{
public:
using Key = T;
myTemplate(Key);
template<class A>
friend std::ostream & operator<<(std::ostream &, const myTemplate<A> &);
private:
struct Thing;
Thing A;
Thing* B;
void disp(std::ostream &) const;
template <class A> friend std::ostream & operator<<(std::ostream &, typename myTemplate<A>::Thing);
template <class A> friend std::ostream & operator<<(std::ostream &, typename myTemplate<A>::Thing *);
};
template <class T> struct myTemplate<T>::Thing
{
T data;
Thing() = default;
Thing(Key);
};
//Create new thing A with B a pointer to A
template <class T> myTemplate<T>::myTemplate(Key Num)
{
A = Thing(Num);
B = &A;
}
//Displays Node A & B
template <class T> void myTemplate<T>::disp(std::ostream & os) const
{
os << A << std::endl; os << B << std::endl;
}
template <class T> myTemplate<T>::Thing::Thing(Key Num)
{
data = Num;
}
//Overloading << will call disp function, in turn print A & B to stream
template<class T> std::ostream & operator<<(std::ostream & os, const myTemplate<T> & c)
{
c.disp(os); return os;
}
//Output a to stream
template <class A> std::ostream & operator<<(std::ostream & os, typename myTemplate<A>::Thing th)
{
os << th.data; return os;
}
//Output a to stream
template <class A> std::ostream & operator<<(std::ostream & os, typename myTemplate<A>::Thing *th)
{
os << th->data; return os;
}
However with myTemplate
when I tried in the main()
:
myTemplate Template(5);
cout << Template;
The code will not compile as I get the error:
Error C2679 binary '<<': no operator found which takes a right-hand operand of type 'const myTemplate<std::string>::Thing' (or there is no acceptable conversion)
Additionally commenting out the line:
os << A << std::endl;
So only B
is being outputted to the stream, the code will compile. However the data of B
is not outputted, only the memory address of B
.
I have noticed that using breakpoints when trying to output B
the code does not even use the overload function I defined. This is not the case for the non-templated class as the overloads I defined are used for both A
and B
.
So what is the correct way to overload the ostream operator to work for the struct member?
Apologises for the long winded question, felt I should include what I had determined myself.
Upvotes: 3
Views: 1189
Reputation: 33092
Since template implementation is going to be in a single translate unit(header file) itself, you are not going to gain anything more, by tearing the things apart. Therefore, keep the definitions and non-member functions inside the class itself, which will provide you with much clear code and also improve the readability of the template class: See this
#include <iostream>
template <class T> class myTemplate
{
public:
using Key = T;
private:
struct Thing
{
T data;
Thing() = default;
Thing(Key Num) : data(Num) {}
};
Thing A;
Thing* B = nullptr;
public:
myTemplate(Key Num) : A(Thing(Num)), B(&A) {}
friend std::ostream & operator<<(std::ostream& out, const myTemplate &obj)
{
return out << obj.A << std::endl << obj.B << std::endl;
}
friend std::ostream & operator<<(std::ostream& out, typename myTemplate::Thing thing)
{
return out << thing.data;
}
friend std::ostream & operator<<(std::ostream& out, typename myTemplate::Thing *thing)
{
return out << thing->data;
}
};
int main()
{
myTemplate<int> obj(10);
std::cout << obj;
return 0;
}
Update: If the final aim of providing that two operator<<
overloads to the struct Thing
is just to conveniently call in the operator<<
of the myTemplate
class, then you do not need that, rather simply print the data
directly in operator<<
of the myTemplate
class. That will again reduce significate amount of codes(if that is the case!).
Nevertheless, now you can provide a specialization for the non-member(friend) function(i.e, operator<<
) for the myTemplate
class, as follows:
template <class T> class myTemplate; // forward declaration
template <class T> std::ostream& operator<<(std::ostream& out, const myTemplate<T> &obj);
template <class T> class myTemplate
{
private:
using Key = T;
private:
template <class Type = T> struct Thing
{
Type data;
Thing() = default;
Thing(const Key Num) : data(Num) {}
};
private:
Thing<> A;
Thing<> *B = nullptr;
public:
myTemplate(const Key Num) : A(Thing<>(Num)), B(&A) {}
friend std::ostream & operator<<<>(std::ostream& out, const myTemplate &obj);
};
template <class T> std::ostream & operator<<(std::ostream& out, const myTemplate<T> &obj)
{
return out << obj.A.data << std::endl << obj.B->data << std::endl;
}
Upvotes: 4