Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++ stringstream to ostream to string

Tags:

c++

stl

I would like to be able to do:

foo(stringstream()<<"number = " << 500);

EDIT: single line solution is crucial since this is for logging purposes. These will be all around the code.

inside foo will print the string to screen or something of the sort.

now since stringstream's operator<< returns ostream&, foo's signature must be:

foo(ostream& o);

but how can I convert ostream& to string? (or char*). Different approaches to achieving this use case are welcome as well.

like image 957
Leo Avatar asked Nov 02 '11 10:11

Leo


People also ask

How do I assign a Stringstream to a string in C++?

std::stringstream::str() is the method you are looking for. std::stringstream is a more generic tool. You can use the more specialized class std::ostringstream for this specific job. If you are working with std::wstring type of strings, you must prefer std::wstringstream or std::wostringstream instead.

Is Stringstream a string?

A stringstream associates a string object with a stream allowing you to read from the string as if it were a stream (like cin). To use stringstream, we need to include sstream header file. The stringstream class is extremely useful in parsing input.

Can we use Stringstream in C?

How to Perform Extraction or Read Operation in StringStream in C++ Like the insertion, we can also perform extraction on StringStream in C++, like the cin >> operator. We can again do this by using the >> operator or the str() function.

What does Stringstream return in C++?

std::stringstream::str The first form (1) returns a string object with a copy of the current contents of the stream. The second form (2) sets s as the contents of the stream, discarding any previous contents.


1 Answers

The obvious solution is to use dynamic_cast in foo. But the given code still won't work. (Your example will compile, but it won't do what you think it should.) The expression std::ostringstream() is a temporary, you can't initialize a non-const reference with a temporary, and the first argument of std::operator<<( std::ostream&, char const*) is a non-const reference. (You can call a member function on a temporary. Like std::ostream::operator<<( void const* ). So the code will compile, but it won't do what you expect.

You can work around this problem, using something like:

foo( std::ostringstream().flush() << "number = " << 500 );

std::ostream::flush() returns a non-const reference, so there are no further problems. And on a freshly created stream, it is a no-op. Still, I think you'll agree that it isn't the most elegant or intuitive solution.

What I usually do in such cases is create a wrapper class, which contains it's own std::ostringstream, and provides a templated member operator<< which forwards to the contained std::ostringstream. Your function foo would take a const reference to this—or what I offen do is have the destructor call foo directly, so that the client code doesn't even have to worry about it; it does something like:

log() << "number = " << 500;

The function log() returns an instance of the wrapper class (but see below), and the (final) destructor of this class calls your function foo.

There is one slight problem with this. The return value may be copied, and destructed immediately after the copy. Which will wreck havoc with what I just explained; in fact, since std::ostringstream isn't copyable, it won't even compile. The solution here is to put all of the actual logic, including the instance of std::ostringstream and the destructor logic calling foo in a separate implementation class, have the public wrapper have a boost::shared_ptr to it, and forward. Or just reimplement a bit of the shared pointer logic in your class:

class LogWrapper
{
    std::ostringstream* collector;
    int* useCount;
public:
    LogWrapper()
        : collector(new std::ostringstream)
        , useCount(new int(1))
    {
    }

    ~LogWrapper()
    {
        -- *useCount;
        if ( *useCount == 0 ) {
            foo( collector->str() );
            delete collector;
            delete useCount;
        }
    }

    template<typename T>
    LogWrapper& operator<<( T const& value )
    {
        (*collector) << value;
        return *this;
    }
};

Note that it's easy to extend this to support optional logging; just provide a constructor for the LogWrapper which sets collector to NULL, and test for this in the operator<<.

EDITED:

One other thing occurs to me: you'll probably want to check whether the destructor is being called as a result of an exception, and not call foo in that case. Logically, I'd hope that the only exception you might get is std::bad_alloc, but there will always be a user who writes something like:

log() << a + b;

where the + is a user defined overload which throws.

like image 99
James Kanze Avatar answered Oct 09 '22 04:10

James Kanze