如何为模板类的成员结构体重载 ostream 运算符

3
我正在使用二叉树在C++中实现一个字典,我的二叉树中的每个节点都有一个键(int)、项目(string)以及左、右孩子。
在这个实现过程中,我重载了我的BinaryTree类的ostream运算符,以打印出树的内容。
此外,我还重载了ostream,以便与Node指针一起使用,然后打印出该节点的keyitem
这个方法效果很好。但是,当我试图将树变成模板来处理任何类型的键或项时,重载这些运算符就变得更加困难了。
我将问题分离出来,使它更容易处理。此外,我尝试使用nodenode指针来玩耍,看看是否可以让其中一个工作而不需要另一个。
以下是我制作的用于测试问题的类,此类未被模板化,效果良好。 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;
}

通过这个类,我可以轻松地创建一个实例并使用std::cout输出期望的结果。 然后将这个类转换为模板:
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;
}

然而,当我在main()中尝试使用myTemplate时:

myTemplate Template(5);    
cout << Template;

代码无法编译,因为出现了以下错误:
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)

此外,将该行注释掉:
os << A << std::endl;

只有 B 被输出到流中,代码将会编译。然而,B 的数据没有被输出,只有 B 的内存地址。

我注意到在尝试输出 B 时使用断点,代码甚至没有使用我定义的重载函数。这不适用于非模板类,因为我定义的重载对于 AB 都适用。

那么,重载 ostream 运算符以使其适用于结构体成员的正确方法是什么?

抱歉问题有些冗长,但我觉得应该包括我自己所确定的内容。


为什么不通过const&传递Thing - Matthieu Brucher
1
你应该提供一个 [mcve]。人们通常不愿意阅读太多的文本和代码。你的问题归结为一个简单的模板类,只有一个 operator<<。在你的问题中写尽可能少的代码,这样你会吸引更多的关注。tl;dr. - YSC
@MatthieuBrucher 通过const传递参数仍然存在相同的问题。 - Josh Paveley
@YSC 是的,我也觉得太长了,谢谢你的建议。 - Josh Paveley
1个回答

4

因为模板实现将在单个翻译单元(头文件)中进行,所以分开处理不会带来更多好处。因此,请将定义和非成员函数放在类内部,这将为您提供更清晰的代码并提高模板类的可读性:请参见此处

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

更新:如果提供Thing结构体的两个operator<<重载的最终目的只是为了方便调用myTemplate类的operator<<,那么您不需要这样做,而是直接在myTemplate类的operator<<中直接打印data。这将再次减少大量代码(如果是这种情况的话!)。

尽管如此,现在您可以为非成员(友元)函数(即operator<<)提供myTemplate类的特化,如下所示:

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

只有一个问题:是否可以使用通用引用来完成,并最终得到一个operator<<函数? - Blood-HaZaRd
1
@Blood-HaZaRd 我之前没有尝试过。但是听起来应该是可能的,如果我们已经为所有想要通过模板类打印的用户定义类型定义了 operator<< - JeJo
这种做法总是最好的吗?即使在有很多成员函数的类中?将它们分开放在更大的类中不会更整洁吗? - Josh Paveley
在我看来,如果它不是模板类会更整洁。否则,将模板定义放在声明旁边是很好的做法。 - JeJo
@JoshPaveley 在你的情况下,问题是Thing是一个私有成员,我不知道如何前置声明。否则(如果它是另一个单独的结构体),你可以选择像这样分离一切。 - JeJo
@JeJo 因为你提供的第一个例子有效(其中一切都在类中定义),这是否意味着我的代码问题在于定义了我已经声明的模板函数?而且那个问题是由模板定义引起的吗?对我来说,看起来我们的代码做的是相同的事情,只是你的写得更清晰。只是想确保我完全理解了所有内容。 - Josh Paveley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接