简体   繁体   中英

How to overload ostream for a member struct of a templated class

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.

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(ie, 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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM