C++中如何链式使用std::cout的<<运算符?

12

可能是重复问题:
在重载operator<<时,std::endl的类型未知
操作符重载

我正在编写一个日志记录器类,但是operator<<方法导致编译错误。这是类的最小化版本,在文件"logger.h"中:

#include <iostream>
class Logger {
public:
    Logger() : m_file(std::cout) {}

    template <typename T>
    Logger &operator<<(const T &a) {
        m_file<<a;
        return *this;
    }

protected:
    std::ostream& m_file;
};

这段代码包含在我的main.cpp中,在输出字符串字面量时可以完美运行:

log << "hi"; 

然而,以下内容将无法编译。

#include "logger.h"
int main() {
    Logger log;

    log << std::endl;
}

g++编译器报告:

src/main.cpp:5: 错误:'log << std::endl'中没有匹配的'operator<<'。

2个回答

13

你的问题不在于 << 连锁反应,一个单独的 log << endl 也会导致问题。问题出在 std::endl 是一个模板函数:

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

basic_ostream中的operator<<重载之一是:

template <class charT, class traits = char_traits<charT> >
class basic_ostream : virtual public basic_ios<charT,traits> {
public:
    basic_ostream<charT,traits>& operator<<(
    basic_ostream<charT,traits>& (*pf)(basic_ostream<charT,traits>&));
//...
};

因此,当使用std::cout<<std::endl时,模板参数可以被推导出来。然而,当左侧为class Logger时,编译器无法推导出endl的模板参数。显式给出模板参数可以让程序编译并工作:

#include <iostream>
class Logger
{
public:
    std::ostream &m_file;
    Logger(std::ostream &o = std::cout):m_file(o){};

    template <typename T>
    Logger &operator<<(const T &a) {
        m_file<<a;
        return *this;
    }
};

int main()
{
    Logger log;
    log<<std::endl<char, std::char_traits<char> >;
    log<<"hi"<<" stackoverflow"<<std::endl<char, std::char_traits<char> >;
    return 0;
}

或者您可以在Logger类中添加一个新的重载operator<<,让编译器推断出std::endl的模板参数:

#include <iostream>
class Logger
{
public:
    std::ostream &m_file;
    Logger(std::ostream &o = std::cout):m_file(o){};

    template <typename T>
    Logger &operator<<(const T &a) {
        m_file<<a;
        return *this;
    }

    Logger &operator<<(std::ostream& (*pf) (std::ostream&)){
        m_file<<pf;
        return *this;
    }
};

int main()
{
    Logger log;
    log<<std::endl;
    log<<"hi"<<" stackoverflow"<<std::endl;
    return 0;
}

此外,如果您不需要立即刷新输出,可以使用 '\n' 代替 endl

2
另外,你可以使用'\n'代替endl。但要注意,如果OP想确保输出缓冲区被刷新,就不能这样做,正如我最近了解到的那样(https://dev59.com/rWsy5IYBdhLWcg3wzBF4)。 - Some programmer dude
好的,我会把它加入到我的答案中。 - fefe

0

3
我认为Eric的意思是std::endl是一个函数。 - Frédéric Hamidi
我想你是指"std::endl是一个函数模板"吧? - Mike Seymour
@Mike,std::endl本身并不是一个模板。是的,它接受和返回一个模板类,但它只是一个成员函数。它的声明看起来像这样:template<class charT, class traits> basic_ostream<charT, traits>& endl(basic_ostream<charT, traits>& os); - Frédéric Hamidi
@jalf,请纠正措辞,我是指“std::endl” ;) - Eric Z
@FrédéricHamidi:这看起来很像一个函数模板。而且它不是任何东西的成员。 - Mike Seymour
啊,好的,这样就更有意义了。 :) - jalf

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