为什么`<< std :: endl`没有调用我希望它调用的运算符?

时间:2016-06-28 11:28:13

标签: c++ operator-overloading

我一直在寻找一种解决方案来同时写入文件和控制台。我找到了一个很好的解决方案here

当我在C ++ 11之前工作时,我不得不对Orbit中Lightness Races的代码进行一些小改动:

#include <iostream>
#include <fstream>
#include <string>

struct OutputAndConsole : std::ofstream
{
    OutputAndConsole(const std::string& fileName)
       : std::ofstream(fileName.c_str())   // constructor taking a string is C++11
       , fileName(fileName)
    {};

    const std::string fileName;
};

template <typename T>
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var)
{
    std::cout << var;    
    static_cast<std::ofstream&>(strm) << var;
    return strm;
};

除了小事以外,它很好用。如果我这样使用它:

int main(){
    OutputAndConsole oac("testLog.dat");
    double x = 5.0;
    oac << std::endl;
    static_cast<OutputAndConsole&>(oac << "foo \n" << x << "foo").operator<<(std::endl);  
    oac << "foo" << std::endl;
}

然后,当控制台上的输出正确显示在文件中时,将忽略所有std::endl。我的猜测是,当我使用std::endl时,调用ostream::operator<<将打印到文件而不是控制台。 static_cast<OutputAndConsole&>的行是我试图调用正确的运算符,但仍然只有来自\n的换行符出现在控制台上。

为什么std::endl调用错误的运算符?

如何拨打正确的电话?

PS :我知道我可以毫无问题地使用\n,但我仍然想知道这里发生了什么以及如何解决它。

5 个答案:

答案 0 :(得分:12)

struct OutputAndConsole : std::ofstream
{
  // ...
};

template <typename T>
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var);

正如其他人所提到的,std::endl是一个模板功能。模板函数不是值,它只是一个名称。

如果您尝试将模板函数传递给期望兼容签名功能的函数,则可以将模板函数转换为值。如果传递给采用Tconst T&的模板函数,则转换为值,因为模板函数的名称代表整个主机可能的价值观。

由于std::endl不是自定义编写的operator<<的有效参数,因此它会查找其他地方。它找到std::ofstream&#39; s operator<<通过显式函数指针获取io操纵器函数。

那个有效,它可以将endl转换为该函数指针类型!所以,兴高采烈地称之为。

要解决此问题,请向operator<<添加OutputAndConsole重载,该重载需要io操纵器函数指针。

最简单的方法是编写辅助函数:

template <class T>
void output_to(OutputAndConsole& strm, const T& var)
{
  std::cout << var;    
  static_cast<std::ofstream&>(strm) << var;
};

然后两个<<重载:

template<class T>
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var) {
  output_to(strm, var);
  return strm;
}
OutputAndConsole& operator<<(OutputAndConsole& strm, std::ostream& (*var)(std::ostream&)) {
  output_to(strm, var);
  return strm;
}

会导致std::endl模板找到匹配的<<

答案 1 :(得分:11)

让我们尝试更简单的事情:

#include <iostream>

struct Foo { };

template <typename T>
Foo& operator<<(Foo& foo, const T& var)
{
    std::cout << var;
    return foo;
};

int main(){
    Foo foo;
    foo << std::endl;
}

这不编译:

a1.cpp: In function ‘int main()’:
a1.cpp:14:9: error: no match for ‘operator<<’ (operand types are ‘Foo’ and ‘<unresolved overloaded function type>’)
     foo << std::endl;
         ^
a1.cpp:14:9: note: candidates are:
a1.cpp:6:6: note: template<class T> Foo& operator<<(Foo&, const T&)
 Foo& operator<<(Foo& foo, const T& var)
      ^
a1.cpp:6:6: note:   template argument deduction/substitution failed:
a1.cpp:14:17: note:   couldn't deduce template parameter ‘T’

为什么呢?编译器试图告诉我们什么?

std :: endl的定义可以在这里找到: http://en.cppreference.com/w/cpp/io/manip/endl

template< class CharT, class Traits >
std::basic_ostream<CharT, Traits>& endl( std::basic_ostream<CharT, Traits>& os );

所以std::endl不是变量。它是模板。更准确地说,是模板功能。在我的小代码中,编译器无法实例化模板。

当我们直接调用std::cout << std::endl;时,编译器会从std::endl的{​​{1}}和CharT实例化Traits

在您的代码中,编译器使用decltype(std::cout)中的CharTTraits实例化模板,因为std::ofstreamOutputAndConsole的后代}。当std::ofstream尝试输出错误的std::cout实例时,它会失败。

PS:最后一段只是部分正确。当你写

std::endl

其中oac << something; 的类型为T,

理论上,它可以调用两个中的任何一个

something

第一个定义是可能的,因为std::ofstream& std::ofstream::operator<<(T) // or operator<<(const T&) // -- or -- OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var); OutputAndConsole继承了成员函数operator<<。第二种形式由您提供。

std::ofstream是变量时,它使用第二个定义。

something是模板时,它不能使用第二个定义,因为无法确定模板的参数。所以它使用了第一个定义。因此,

something

相当于

oac << std::endl;  // std::endl is a template

我们可以通过以下代码看到它:

static_cast<ofstream&>(oac) << std::endl;

此代码不会打印“X”。

答案 2 :(得分:5)

std::endl是一个函数,而不是字符串。 你的重载方法需要一个字符串来进行重载,所以当你执行<< std::endl

时不会被调用

您需要创建一个运算符,该运算符使用与std:endl具有相同签名的函数来执行重载。

 std::ostream& operator<<( std::ostream& (*f)(std::ostream&) )

答案 3 :(得分:4)

我建议不要通过流接口实现标准I / O流功能,而是使用streambuffer接口。只要流被识别为std :: istream / std :: ostream(由于某些操作符,操作符或函数引用流),定制的流接口通常会导致麻烦。

您可以使用:

#include <array>
#include <iostream>
#include <sstream>
#include <vector>

// BasicMultiStreamBuffer
// ============================================================================

/// A (string) stream buffer for synchronizing writes into multiple attached buffers.
template<class Char, class Traits = std::char_traits<Char>, class Allocator = std::allocator<Char> >
class BasicMultiStreamBuffer : public std::basic_stringbuf<Char, Traits, Allocator>
{
    // Types
    // =====

    private:
    typedef typename std::basic_stringbuf<Char, Traits> Base;

    public:
    typedef typename std::basic_streambuf<Char, Traits> buffer_type;
    typedef typename buffer_type::char_type char_type;
    typedef typename buffer_type::traits_type traits_type;
    typedef typename buffer_type::int_type int_type;
    typedef typename buffer_type::pos_type pos_type;
    typedef typename buffer_type::off_type off_type;

    private:
    typedef typename std::vector<buffer_type*> container_type;

    public:
    typedef typename container_type::size_type size_type;
    typedef typename container_type::value_type value_type;
    typedef typename container_type::reference reference;
    typedef typename container_type::const_reference const_reference;
    typedef typename container_type::iterator iterator;
    typedef typename container_type::const_iterator const_iterator;


    // Construction/Destructiion
    // =========================

    public:
    BasicMultiStreamBuffer()
    {}

    template <typename...Buffers>
    BasicMultiStreamBuffer(Buffers* ...buffers) {
        std::array<buffer_type*, sizeof...(Buffers)> buffer_array{buffers...};
        m_buffers.reserve(buffer_array.size());
        for(auto b : buffer_array) {
            if(b)
                m_buffers.push_back(b);
        }
    }

    template <typename Iterator>
    BasicMultiStreamBuffer(Iterator first, Iterator last)
    :   m_buffers(first, last)
    {}

    ~BasicMultiStreamBuffer() {
        sync();
    }


    private:
    BasicMultiStreamBuffer(BasicMultiStreamBuffer const&); // No Copy.
    BasicMultiStreamBuffer& operator=(BasicMultiStreamBuffer const&); // No Copy.


    // Capacity
    // ========

    public:
    bool empty() const { return m_buffers.empty(); }
    size_type size() const { return m_buffers.size(); }


    // Iterator
    // ========

    public:
    iterator begin() { return m_buffers.begin(); }
    const_iterator begin() const { return m_buffers.end(); }
    iterator end() { return m_buffers.end(); }
    const_iterator end() const { return m_buffers.end(); }


    // Modifiers
    // =========

    public:
    /// Attach a buffer.
    void insert(buffer_type* buffer) {
        if(buffer) m_buffers.push_back(buffer);
    }

    /// Synchronize and detach a buffer.
    void erase(buffer_type* buffer) {
        iterator pos = this->begin();
        for( ; pos != this->end(); ++pos) {
            if(*pos == buffer) {
                char_type* p = this->pbase();
                std::streamsize n = this->pptr() - p;
                if(n)
                    sync_buffer(*pos, p, n);
                m_buffers.erase(pos);
                break;
            }
        }
    }


    // Synchronization
    // ===============

    private:
    int sync_buffer(buffer_type* buffer, char_type* p, std::streamsize n) {
        int result = 0;
        std::streamoff offset = 0;
        while(offset < n) {
            int k = buffer->sputn(p + offset, n - offset);
            if(0 <= k) offset += k;
            else {
                result = -1;
                break;
            }
            if(buffer->pubsync() == -1)
                result = -1;
        }
        return result;
    }

    protected:
    /// Synchronize with the attached buffers.
    /// \ATTENTION If an attached buffer fails to synchronize, it gets detached.
    virtual int sync() override {
        int result = 0;
        if( ! m_buffers.empty()) {
            char_type* p = this->pbase();
            std::streamsize n = this->pptr() - p;
            if(n) {
                iterator pos = m_buffers.begin();
                while(pos != m_buffers.end()) {
                    if(0 <= sync_buffer(*pos, p, n)) ++pos;
                    else {
                        pos = m_buffers.erase(pos);
                        result = -1;
                    }
                }
            }
        }
        this->setp(this->pbase(), this->epptr());
        if(Base::sync() == -1)
            result = -1;
        return result;
    }

    private:
    container_type m_buffers;
};

typedef BasicMultiStreamBuffer<char> OStreamBuffers;


// BasicMultiStream
// ============================================================================

template<class Char, class Traits = std::char_traits<Char>, class Allocator = std::allocator<Char> >
class BasicMultiStream : public std::basic_ostream<Char, Traits>
{
    // Types
    // =====

    private:
    typedef std::basic_ostream<Char, Traits> Base;

    public:
    typedef BasicMultiStreamBuffer<Char, Traits, Allocator> multi_buffer;
    typedef std::basic_ostream<Char, Traits> stream_type;

    typedef typename multi_buffer::buffer_type buffer_type;
    typedef typename multi_buffer::char_type char_type;
    typedef typename multi_buffer::traits_type traits_type;
    typedef typename multi_buffer::int_type int_type;
    typedef typename multi_buffer::pos_type pos_type;
    typedef typename multi_buffer::off_type off_type;

    typedef typename multi_buffer::size_type size_type;
    typedef typename multi_buffer::value_type value_type;
    typedef typename multi_buffer::reference reference;
    typedef typename multi_buffer::const_reference const_reference;
    typedef typename multi_buffer::iterator iterator;
    typedef typename multi_buffer::const_iterator const_iterator;


    // Construction
    // ============

    public:
    BasicMultiStream()
    :   Base(&m_buffer)
    {}

    template <typename ...Streams>
    BasicMultiStream(Streams& ...streams)
    :   Base(&m_buffer), m_buffer(streams.rdbuf()...)
    {}

    private:
    BasicMultiStream(const BasicMultiStream&); // No copy.
    const BasicMultiStream& operator = (const BasicMultiStream&); // No copy.

    // Capacity
    // ========

    public:
    bool empty() const { return m_buffer.empty(); }
    size_type size() const { return m_buffer.size(); }


    // Iterator
    // ========

    public:
    iterator begin() { return m_buffer.begin(); }
    const_iterator begin() const { return m_buffer.end(); }
    iterator end() { return m_buffer.end(); }
    const_iterator end() const { return m_buffer.end(); }


    // Modifiers
    // =========

    public:
    template <typename StreamIterator>
    void insert(StreamIterator& first, StreamIterator& last)
    {
        while(first != last)
            insert(*first++);
    }
    void insert(stream_type& stream) { m_buffer.insert(stream.rdbuf()); }
    void erase(stream_type& stream) { m_buffer.erase(stream.rdbuf()); }

    private:
    multi_buffer m_buffer;
};

typedef BasicMultiStream<char> MultiStream;


int main() {
    MultiStream s(std::cout, std::cerr, std::clog);
    s << "Hello World" << std::endl;
    printf("[Three lines of output]\n");
}

注意,应用更改到std :: basic_streambuf接口的唯一函数是virtual int sync() override

除了派生和初始化自定义流类之外,标准基本流类不提供任何接口。实际(虚拟)接口是标准流缓冲区basic_streambuf

答案 4 :(得分:3)

为了使它工作,我会创建自己的一组操纵器:

struct ManipEndl {};
const ManipEndl manip_endl;
OutputAndConsole& operator<<(OutputAndConsole& strm, const ManipEndl& foo)
{
    std::cout << std::endl;    
    static_cast<std::ofstream&>(strm) << '\n'; // no need for std::endl here
    return strm;
};

int main(){
    OutputAndConsole oac("testLog.dat");
    double x = 5.0;
    oac << manip_endl;
    oac << x << manip_endl << "foo" << manip_endl;  
    oac << "foo" << manip_endl;
}