stuck --- 在 C++ 中创建运算符模板

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

我正在创建一个模仿 cout 的 class SpyOutput,我正在尝试使用一个模板,这样我就不必重载 << 运算符 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;
                       ^

错误信息的关键部分是"template argument deduction/substitution failed"。 endl 不是对象,甚至不是函数。它是一个函数 template,这是一个 "cookie cutter" 用于生成无限数量的 cookie 函数。当 <<ostream 时,编译器会查看是否有任何潜在的 << 函数可以阐明模板参数应该是什么。并且存在这个过载:

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

当编译器检查此重载时,它意识到它可以明确地将 endl 特化为 ostream& (*)(ostream&),因此选择此重载并执行匹配流类型的隐式特化。幸运的是,您所要做的就是提供上述函数重载,希望这个魔法对您也有用。

请注意,ostreams 还有两个重要的重载作为您必须添加的成员:

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

还值得一提的是,您的函数正在尝试复制您流式传输的所有对象,这可能会导致它失败或行为异常。一个更明智的想法是使用通用引用。或者在 至少 通过 const 引用捕获。

//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 是一个函数 template,因此编译器需要知道您要使用模板的哪个实例化。标准 IOStream classes 有单独的操纵器重载,它们明确指定模板实例化。你也可以这样做:

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

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