【问题标题】:C++ stream as a parameter when overloading operator<<重载 operator<< 时将 C++ 流作为参数
【发布时间】:2024-04-30 19:45:01
【问题描述】:

我正在尝试编写自己的日志记录类并将其用作流:

logger L;
L << "whatever" << std::endl;

这是我开始使用的代码:

#include <iostream>

using namespace std;


class logger{
public:
    template <typename T>
    friend logger& operator <<(logger& log, const T& value);
};

template <typename T>
logger& operator <<(logger& log, T const & value) {
    // Here I'd output the values to a file and stdout, etc.
    cout << value;
    return log;
}

int main(int argc, char *argv[])
{
    logger L;
    L << "hello" << '\n' ; // This works
    L << "bye" << "alo" << endl; // This doesn't work
    return 0;
}

但我在尝试编译时遇到错误,说没有 operator

pruebaLog.cpp:31: error: no match for ‘operator<<’ in ‘operator<< [with T = char [4]](((logger&)((logger*)operator<< [with T = char [4]](((logger&)(& L)), ((const char (&)[4])"bye")))), ((const char (&)[4])"alo")) << std::endl’

所以,我一直试图重载 operator

logger& operator <<(logger& log, const basic_ostream<char,char_traits<char> >& (*s)(basic_ostream<char,char_traits<char> >&))

但没有运气。我尝试过使用模板而不是直接使用 char,也尝试过简单地使用“const ostream& os”,但什么也没有。

让我烦恼的另一件事是,在错误输出中,operator

【问题讨论】:

  • 在重载操作符时,[std::endl 的可能重复是未知类型*.com/questions/1134388/…)

标签: c++ operator-overloading stream endl


【解决方案1】:

endl 是一头奇怪的野兽。它不是一个常数值。它实际上是一个函数。你需要一个特殊的覆盖来处理endl的应用:

logger& operator<< (logger& log, ostream& (*pf) (ostream&))
{
  cout << pf;
  return log;
}

这接受插入一个接受ostream 引用并返回ostream 引用的函数。这就是endl

编辑:回应 FranticPedantic 的有趣问题“为什么编译器不能自动推断?”。原因是,如果你深入研究,endl 实际上本身就是一个 template 函数。定义为:

template <class charT, class traits>
  basic_ostream<charT,traits>& endl ( basic_ostream<charT,traits>& os );

也就是说,它可以将任何类型的ostream 作为其输入和输出。所以问题不在于编译器无法推断出 T const &amp; 可能是一个函数指针,而是它无法确定 which endl 你打算传入。模板化的问题中提出的operator&lt;&lt; 版本将接受指向任何函数的指针作为其第二个参数,但同时,endl 模板代表一组无限潜在函数,因此编译器在那里无法做任何有意义的事情。

提供operator&lt;&lt; 的特殊重载,其第二个参数匹配endl 模板的特定 实例化,允许调用解析。

【讨论】:

  • 谢谢!那解决了它。现在我看到了您的解决方案,我发现我的定义 basic_ostream&lt;char,char_traits&lt;char&gt; &gt;&amp; (*s)(basic_ostream&lt;char,char_traits&lt;char&gt; &gt;&amp;) 是 ostream 的通用版本,因为它被定义为“const”而不起作用。一旦我放弃了 const,它就起作用了。
  • 对我来说真正的问题仍然存在,这就是为什么编译器不能将 T 推断为函数指针并绑定到它?
【解决方案2】:

endl 是一个 IO 操纵器,它是一个函子,它通过引用接受流,对其执行一些操作,然后通过引用返回该流。 cout &lt;&lt; endl 等价于cout &lt;&lt; '\n' &lt;&lt; flush,其中flush 是一个刷新输出缓冲区的操纵器。

在你的类中,你只需要为这个操作符写一个重载:

logger& operator<<(logger&(*function)(logger&)) {
    return function(*this);
}

其中logger&amp;(*)(logger&amp;) 是通过引用接受和返回logger 的函数的类型。要编写自己的操纵器,只需编写一个与该签名匹配的函数,并让它对流执行一些操作:

logger& newline(logger& L) {
    return L << '\n';
}

【讨论】:

  • 这比公认的答案更复杂,但仍然是一种有趣的方法。
  • @FranticPedantic:同一枚硬币的两面,真的。
【解决方案3】:

我认为问题在于您的流不会重载 operator&lt;&lt; 以接受与 std::endl 具有相同类型的函数,如以下答案所示:std::endl is of unknown type when overloading operator<<

【讨论】:

    【解决方案4】:

    在 C++ 中,流缓冲区封装了底层 I/O 机制。流本身只封装了对字符串的转换,以及 I/O 方向。

    因此,您应该使用预定义的流类之一,而不是自己制作。如果您希望 I/O 进入一个新目标(例如系统日志),那么您应该创建自己的 流缓冲区(源自 std::streambuf)。

    【讨论】: