Using << operator to write to both a file and cout
Asked Answered
N

6

5

I'd like to overload << operator to write the value it takes to a file and cout. I have tried to do it with following code, but couldn't succeed it. It just writes the value to text file. Any help would be appreciated. Thank you.

void operator<<(std::ostream& os, const string& str)
{
    std::cout << str;
    os << str;
}

int main() {
    ofstream fl;
    fl.open("test.txt");
    fl << "!!!Hello World!!!" << endl;
    return 0;
}
Necktie answered 25/6, 2014 at 16:13 Comment(5)
Explain what you mean by "but couldn't succeed it."Gemini
Looks like inadvertent infinite recursion. What you want instead is a ostream with a streambuffer that transparently multiplexes to any number of other streams. There is no standard one.Engedi
Nearly a dupe of https://mcmap.net/q/1921429/-class-to-output-to-several-ostreams-file-and-console/179910.Harem
Overloading operators for standard types is bad enough, but when that operator is already overloaded?Fax
Thanks a lot I'll check the other topic out.Necktie
G
10

Create a helper class and overload operators that takes care of streaming to two streams. Use the helper class instead of trying to override the standard library implementations of the overloaded operator<< functions.

This should work:

#include <iostream>
#include <fstream>

struct MyStreamingHelper
{
    MyStreamingHelper(std::ostream& out1,
                      std::ostream& out2) : out1_(out1), out2_(out2) {}
    std::ostream& out1_;
    std::ostream& out2_;
};

template <typename T>
MyStreamingHelper& operator<<(MyStreamingHelper& h, T const& t)
{
   h.out1_ << t;
   h.out2_ << t;
   return h;
}

MyStreamingHelper& operator<<(MyStreamingHelper& h, std::ostream&(*f)(std::ostream&))
{
   h.out1_ << f;
   h.out2_ << f;
   return h;
}

int main()
{
   std::ofstream fl;
   fl.open("test.txt");
   MyStreamingHelper h(fl, std::cout);
   h << "!!!Hello World!!!" << std::endl;
   return 0;
}
Gemini answered 25/6, 2014 at 16:20 Comment(5)
Fails on std::flush or std::endl.. says: no match for 'operator<<' (operand types are 'xamid::helper::MyStreamingHelper' and '<unresolved overloaded function type>').Rojas
@xamid, works for me on my desktop as well as at ideone.com/a4QszP.Gemini
I am using MinGW-w64 compiler on Windows 7, c++11.Rojas
@xamid, I don't know how to help you there. Good luck.Gemini
None of the approaches that I found on the internet worked for me. Most had bugs like stopping to write to file after flush, some did not compile. So I tried Boost (boost::iostreams::tee_device) and it works just fine.Rojas
A
4

If you are able to use it, you will find that, not unsurprisingly, the boost library has already done most of the hard work for you.

#include <iostream>
#include <fstream>
#include <boost/iostreams/tee.hpp>
#include <boost/iostreams/stream.hpp>

typedef boost::iostreams::tee_device<std::ostream, std::ostream> teedev;
typedef boost::iostreams::stream<teedev, std::char_traits<typename std::ostream::char_type>, std::allocator< typename std::ostream::char_type > > tee_stream;

int main(int argc, char* argv[])
{
    std::ofstream of;
    of.open( "test.txt" );

    teedev td( of, std::cout );
    tee_stream ts(td);

    ts << "!!!Hello World!!!" << std::endl;

    return 0;
}
Attached answered 25/6, 2014 at 23:4 Comment(1)
Ah sure, did't think of checking boost library. Thanks.Necktie
S
3

To implement a full stream interface you should build a stream buffer and a stream:

#include <ostream>
#include <sstream>
#include <streambuf>
#include <vector>

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

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()
    {}

    BasicMultiStreamBuffer(buffer_type* a) {
        if(a) {
            m_buffers.reserve(1);
            m_buffers.push_back(a);
        }
    }

    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:
    void insert(buffer_type* buffer) {
        if(buffer) m_buffers.push_back(buffer);
    }

    void erase(buffer_type* buffer) {
        iterator pos = this->begin();
        for( ; pos != this->end(); ++pos) {
            if(*pos == buffer) {
                m_buffers.erase(pos);
                break;
            }
        }
    }


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

    protected:
    virtual int sync() {
        int result = 0;
        if( ! m_buffers.empty()) {
            char_type* p = this->pbase();
            std::streamsize n = this->pptr() - p;
            if(n) {
                const_iterator pos = m_buffers.begin();
                for( ; pos != m_buffers.end(); ++pos) {
                    std::streamoff offset = 0;
                    while(offset < n) {
                        int k = (*pos)->sputn(p + offset, n - offset);
                        if(0 <= k) offset += k;
                        else {
                            result = -1;
                            break;
                        }
                    }
                    if((*pos)->pubsync() == -1) 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)
    {}
    BasicMultiStream(stream_type& stream)
    :   Base(&m_buffer), m_buffer(stream.rdbuf())
    {}

    template <typename StreamIterator>
    BasicMultiStream(StreamIterator& first, StreamIterator& last)
    :   Base(&m_buffer)
    {
        while(first != last) insert(*first++);
    }

    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:
    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;


// Test
// =============================================================================

#include <iostream>
int main() {
    MultiStream out;
    out.insert(std::cout);
    out.insert(std::clog);
    out << "Hello\n";
}

Here, the output is buffered in a string stream buffer and synchronized to the destination streams.

Scintilla answered 25/6, 2014 at 17:32 Comment(4)
Why so complicated? And why inherit from stringbuf, when you can just as easily inherit from streambuf directly?Osiris
@JamesKanze The intention is to have an additional buffer (handling all formatted/unformatted) IO and synchronize (flush to) the other buffers. Also, the multi-stream can be passed as std::ostream& without loosing functionality.Scintilla
But why the iterators and the container interface? What does that have to do with a streambuf? If you need some sort of synchronization, so that both destinations do there physical writes at the same time, containing an std::vector<char> for the buffer, and then doing a sync on the two target streambufs when you transfer it to them, should do the trick.Osiris
When I use an ofstream with this, it just stops writing to its file after the first flush (and a short amout of time). But it only does it when I run the file anone (not via Eclipse). Do you know how to fix this? I use MinGW-w64 and Windows 7.Rojas
C
1

The function you've overloaded takes a left hand side of std::ostream& and a right hand side of const std::string&. This is how you called the function:

fl << "!!!Hello World!!!" << endl;

The left hand side matches perfectly, but the right hand side doesn't. The string you've passed is not an std::string but rather a object of type char const*. The custom overload you made isn't called even though there is a viable conversion from char const* to const std::string& because there is in fact a perfect match elsewhere.

The overload that is a perfect match is the overload found in namespace std which is signed as:

namespace std { // Simplification:
    std::ostream& operator<<(std::ostream& os, char const* str);
}

This is a better match because no conversion needs to be made on the matter of the right hand parameter. Another reason this works is because of something called ADL or Argument Dependent Lookup. Normally you would have to explicitly qualify names or functions from other namespaces (like std) from outside of those namespaces, but when it comes to ADL, if the compiler can find a function which takes a class of user-defined type from that same namespace, calling said function will not require explicit qualification from outside that namespace. Therefore the above is equivalent to:

std::operator<<(f1, "!!!Hello World!!!") << std::endl;

You can see this while using std::getline(). The following is well-formed even though we do not use using namespace std or using std::getline:

getline(std::cin, line);

Because std::cin is within the same namespace as the function std::getline(), you do not need to append std:: to the function call.

So for your overload to be called, there has to be a better match. You can force this by creating a std::string explicitly:

fl << std::string("!!!Hello World!!!") << endl;

Your overload is called and not the one in namespace std because overloads in the enclosing namespace are considered before outer ones like std. But not only is this non-intuitive, it will also cause other issues.

  1. Your function needs to return the type std::ostream& instead of void and have a return os statement so that you can chain the << endl expression.

  2. Inside your function you're performing infinite recursion on the os << str line. There are a number of ways to solve this issue, the easiest being to do os << str.c_str() so that the char const* overload in namespace std gets called.

Your method isn't the best way to do this so for more complete and better solutions, look toward the other answers and comments in this thread.

Calces answered 25/6, 2014 at 16:50 Comment(1)
Thanks a lot for the detailed explanation, it's a lot more clear to me now.Necktie
L
0

It just writes the value to the text file because your operator<< method does not get called.

The operator<< should return a reference to the stream (ostream&) because otherwise << str << endl; would not work.

The other problem with your operator<< is that it could create an infinite loop since os << str; has the same signature than fl << "string";

Lalapalooza answered 25/6, 2014 at 16:21 Comment(0)
O
0

The usual way of doing this is to use a filtering streambuf, which forwards to both of the target streambufs. Something like the following should do the trick:

class LoggingStreambuf : public std::streambuf
{
    std::streambuf* myPrinciple;
    std::ostream*   myOwner;
    std::filebuf    myLogging;
protected:
    int overflow( int ch ) override
    {
        myLogging->sputc( ch );
        return myPrinciple->sputc( ch );
    }
public:
    LoggingStreambuf( std::streambuf* principal, std::string const& logFileName )
        : myPrinciple( principal )
        , myOwner( nullptr )
        , myLogging( logFileName )
    {
    }

    LoggingStreambuf( std::ostream& principal, std::string const& logFileName )
        : myPrinciple( principal.rdbuf() )
        , myOwner( &principal )
        , myLogging( logFileName )
    {
        myOwner.rdbuf( this );
    }

    ~LoggingStreambuf()
    {
        if ( myOwner != nullptr ) {
            myOwner.rdbuf( myPrinciple );
        }
    }
};

(This particular code supposes that the file output is a log file, a secondary output on which errors should be ignored. Other error handling strategies can also be implemented, and of course, there's no reason that you couldn't provide two arbitrary std::streambuf*, and create a new std::ostream outputting through an instance of the custom streambuf.)

To use this class, as it is written:

LoggingStreambuf logger( std::cout, "logfile.txt" );
//  And output to std::cout as usual...

More generally, of course: anytime you want to do something special with regards to the data sink or source of a stream, you implement a new std::streambuf, since this is the class which handles sinking and sourcing of data for the streams.

Osiris answered 26/6, 2014 at 8:32 Comment(1)
Doesn't compile: base operand of '->' has non-pointer type 'std::filebuf {aka std::basic_filebuf<char>}' main.cpp /ProofTool no matching function for call to 'std::basic_filebuf<char>::basic_filebuf(const string&)' request for member 'rdbuf' in '((LoggingStreambuf*)this)->LoggingStreambuf::myOwner', which is of pointer type 'std::ostream* {aka std::basic_ostream<char>*}' (maybe you meant to use '->' ?) request for member 'rdbuf' in '((LoggingStreambuf*)this)->LoggingStreambuf::myOwner', which is of pointer type 'std::ostream* {aka std::basic_ostream<char>*}' (maybe you meant to use '->' ?)Rojas

© 2022 - 2024 — McMap. All rights reserved.