3

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.

JeJo
  • 30,635
  • 6
  • 49
  • 88

1 Answers1

4

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;
}
JeJo
  • 30,635
  • 6
  • 49
  • 88
  • Just one question; could this be done with universal reference and end up with one operator<< function ? – Blood-HaZaRd Nov 12 '18 at 13:59
  • 1
    @Blood-HaZaRd I have not tried it before. But it sound like, should be possible, if we have defined `operator<<` for all the user-defined types what we want to print through the templated class. – JeJo Nov 12 '18 at 14:05
  • Is it always best to use this practice? Even in classes with lots of member functions? Would it not be neater to have the separate in larger classes? – Josh Paveley Nov 12 '18 at 14:13
  • @JoshPaveley **IMHO**, it is neater if it would have been non-templated classes. Otherwise, its good to see the definitions of templates just next to the declaration. – JeJo Nov 12 '18 at 14:22
  • @JoshPaveley In your case the problem was `Thing` is a private member which I do not know how to forward declare. Otherwise(if it would have been another seperate struct), you have the **[choice to seperate everything like this](https://wandbox.org/permlink/9PaP8loNvYXwXpLM)** – JeJo Nov 12 '18 at 14:26
  • @JeJo As the first example you provided works, (where everything is defined in the class) does this mean the problem in my code was in defining the templated functions I had already declared? And that problem was caused by template definitions? As to me it looks like to me our code does the same and yours is just written cleaner. Just want to make sure I understand everything properly. – Josh Paveley Nov 12 '18 at 15:15