当重载operator<<时,std::endl的类型未知。

69

我重载了运算符<<

template <Typename T>
UIStream& operator<<(const T);

UIStream my_stream;
my_stream << 10 << " heads";

可以使用,但是:

my_stream << endl;

编译错误:

错误 C2678: 二进制“<<”:找不到接受左操作数类型为“UIStream”的运算符(或者没有可接受的转换)

如何解决 my_stream << endl 的问题?


不了解UIStream,很难发表评论。 - anon
14
尽管如此,你还是找到了方法。 :) - davidtbernal
6个回答

94

std::endl是一个函数,std::cout通过实现与std::endl具有相同函数签名的函数指针的operator<<来使用它。

在这里,它调用该函数,并向前传递返回值。

这里是一个代码示例:

#include <iostream>

struct MyStream
{
    template <typename T>
    MyStream& operator<<(const T& x)
    {
        std::cout << x;

        return *this;
    }


    // function that takes a custom stream, and returns it
    typedef MyStream& (*MyStreamManipulator)(MyStream&);

    // take in a function with the custom signature
    MyStream& operator<<(MyStreamManipulator manip)
    {
        // call the function, and return it's value
        return manip(*this);
    }

    // define the custom endl for this stream.
    // note how it matches the `MyStreamManipulator`
    // function signature
    static MyStream& endl(MyStream& stream)
    {
        // print a new line
        std::cout << std::endl;

        // do other stuff with the stream
        // std::cout, for example, will flush the stream
        stream << "Called MyStream::endl!" << std::endl;

        return stream;
    }

    // this is the type of std::cout
    typedef std::basic_ostream<char, std::char_traits<char> > CoutType;

    // this is the function signature of std::endl
    typedef CoutType& (*StandardEndLine)(CoutType&);

    // define an operator<< to take in std::endl
    MyStream& operator<<(StandardEndLine manip)
    {
        // call the function, but we cannot return it's value
        manip(std::cout);

        return *this;
    }
};

int main(void)
{
    MyStream stream;

    stream << 10 << " faces.";
    stream << MyStream::endl;
    stream << std::endl;

    return 0;
}
this gives you a better idea of how these things work.

5
请在您给出负评时留下评论,这样我就可以改进我的回答。 - GManNickG
7
我没有投反对票,但这里缺少一个重要的细节:std::endl不是一个函数,而是一个带模板参数的函数。这意味着,如果您尝试定义一个通用操纵器来接受operator<<重载,如下所示:template <typename T> mystream& operator<<( T& (*fp)(T&) )(此签名将接受所有STL basic_stream<>ios_basebasic_ios<>操纵器),编译器将无法将std::endl与模板匹配,因为它本身也是一个模板,并且无法定义T的含义。 - David Rodríguez - dribeas
1
谢谢!这帮助我回答了另一个问题。https://dev59.com/63I95IYBdhLWcg3wsQFm - Nicolás
1
为什么要使用 typedef CoutType 而不是直接使用 ostream - cp.engr

38

问题在于std::endl是一个函数模板,就像你的<<操作符一样。因此,当你写:

my_stream << endl;

您希望编译器推断运算符的模板参数,以及与endl相关的模板参数。这是不可能的。

因此,您必须编写额外的非模板重载函数来处理操纵符。它们的原型将如下:

UIStream& operator<<(UIStream& os, std::ostream& (*pf)(std::ostream&));

(还有两个替换std::ostreamstd::basic_ios<char>std::ios_base的,如果想允许所有操作符,则必须提供它们)它们的实现与您的模板非常相似。 实际上,非常相似,以至于您可以使用您的模板进行如下实现:

typedef std::ostream& (*ostream_manipulator)(std::ostream&);
UIStream& operator<<(UIStream& os, ostream_manipulator pf)
{
   return operator<< <ostream_manipulator> (os, pf);
}

最后提醒一点,通常编写自定义的streambuf往往是实现您正在使用的技术所希望实现的更好方法。


2
+1 这是我昨天提供的相同答案。不幸的是,它被忽略了。https://dev59.com/FUjSa4cB1Zd3GeqPFXKE#1134501 - David Rodríguez - dribeas
实际上,我找到了相同的解决方案,但是我使用了一个更简单的函数体:pf(*this); return *this;,但是我将 op<< 添加为我的派生 ostreamer 类的成员。 - TrueY

10
我做了这件事来解决我的问题,这是我的代码的一部分:
    template<typename T> 
    CFileLogger &operator <<(const T value)
    {
        (*this).logFile << value;
        return *this;
    }
    CFileLogger &operator <<(std::ostream& (*os)(std::ostream&))
    {
        (*this).logFile << os;
        return *this;
    }

Main.cpp

int main(){

    CFileLogger log();    
    log << "[WARNINGS] " << 10 << std::endl;
    log << "[ERRORS] " << 2 << std::endl;
    ...
}

我在这里找到了参考资料:http://www.cplusplus.com/forum/general/49590/

希望这能帮助到某些人。


5
请点击这里查看更好的IOStreams扩展方法。(有点过时,且为VC 6量身定制,因此您需要谨慎对待)
关键是要使函数对象(以及输出"\n"并刷新的endl)起作用,您需要实现完整的ostream接口。

4

std流不是为了子类化而设计的,因为它们没有虚方法,所以我认为你在这方面不会有太大的进展。但你可以尝试聚合一个std::ostream来完成工作。

为了让endl正常工作,你需要实现一个版本的operator<<,该版本接受指向函数的指针,因为操作符例如endl都是通过这种方式处理的。

UStream& operator<<( UStream&, UStream& (*f)( UStream& ) );

或者

UStream& UStream::operator<<( UStream& (*f)( UStream& ) );

现在,std::endl是一个函数,它接受并返回对std::basic_ostream的引用,因此不能直接与您的流一起使用,所以您需要创建自己的版本,调用聚合的std::iostream中的std::endl版本。
编辑:看起来GMan的答案更好。他也使std::endl正常工作了!

我会支持这个答案 :P - GManNickG
实际上不是这样的。如果您愿意阅读我文章中提供的链接,您将会知道如何让所有的函数对象都能工作,而不仅仅是那些您明确实现的。 - EFraim

1

除了已经被接受的答案之外,在C++11中还可以重载 operator<< 来处理该类型:

decltype(std::endl<char, std::char_traits<char>>)

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