卡住了---用C++创建一个运算符模板

stuck --- Creating an Operator Template in C++

本文关键字:一个 运算符 创建 C++      更新时间:2023-10-16

我正在创建一个模仿cout的类SpyOutput,我正在尝试使用一个模板,这样我就不必重载<lt;操作员4次(每种数据类型一次):

#include <iostream>
#include <sstream>
using namespace std;
class SpyOutput
{
    ostream *os;
    stringstream ss;
    int sum, temp;
public: 
    SpyOutput(ostream *s):os(s), sum(0){}
    template <class T>
    SpyOutput& operator<<(T x)
    {
        ss << x;
        *os << x;
        return *this;
    }
};
int main(void)
{
    SpyOutput spy(&cout);
    spy << "Hello" << endl;
    spy << "1235" << endl;
    spy << 'z' << endl;
    spy << 4.56 << endl;
    return 0;
}

我无法编译它,它似乎无法识别我的模板。有什么想法吗?G++错误消息为

main.cpp: In function 'int main()':
main.cpp:24:20: error: no match for 'operator<<' (operand types are 'SpyOutput' and '<unresolved overloaded function type>')
     spy << "Hello" << endl;
                    ^
main.cpp:24:20: note: candidates are:
main.cpp:13:16: note: template<class T> SpyOutput& SpyOutput::operator<<(T)
     SpyOutput& operator<<(T x)
                ^
main.cpp:13:16: note:   template argument deduction/substitution failed:
main.cpp:24:23: note:   couldn't deduce template parameter 'T'
     spy << "Hello" << endl;
                       ^

错误消息的关键部分是"模板参数推导/替换失败"。endl不是一个对象,它甚至不是一个函数。它是一个函数模板,是一个"cookie切割器",用于生成无限数量的cookie函数。当<<转换为ostream时,编译器会查看是否有任何潜在的<<函数可以澄清模板参数应该是什么

ostream& operator<< (ostream& (*pf)(ostream&));

当编译器检查这个重载时,它意识到它可以明确地将endl专门化为ostream& (*)(ostream&),因此选择这个重载并执行与流的类型匹配的隐式专门化。幸运的是,你所要做的就是提供上面的函数重载,希望这个魔术也能为你工作。

需要注意的是,作为成员,ostreams还有另外两个重要的重载:

ostream& operator<< (ios& (*pf)(ios&));
ostream& operator<< (ios_base& (*pf)(ios_base&));

还值得一提的是,您的函数正试图复制您流式传输的所有对象,这可能会导致它失败或操作不当。一个更明智的想法是使用一个普遍的参考。或者在时至少通过常量引用捕获。

//if T is a template, then T&& is a universal reference. A perfect match for everything
//if T were not a template, it would be an rvalue reference. Totally unrelated.
template <class T> SpyOutput& operator<<(T&& x) { 
    ss << x;
    *os << x;
    return *this;
}

您的代码之所以失败,仅仅是因为std::endl是一个函数模板,因此编译器需要知道您要使用模板的哪个实例化。标准IOStream类具有用于操纵器的单独重载,并且它们显式地指定模板实例化。你也可以这样做:

SpyOutput& operator<<(std::ostream& (*manip)(std::ostream&))
{
    if (os) manip(*os);
    return *this;
}

现在,当您执行spy << std::endl时,它将实例化有效的std::endl<char, std::char_traits<char>>,并且代码将工作。尽管一般来说,我不会重新创建整个流类,而是使用std::streambuf接口。