22

The following snippet is simplified version of a logger that I use. It extends std::ostringstream and can be filled using the <<-operator. Upon destruction all content is written to std::cout.

Writing (<<) directly into a temporary object, Logger(), I would expect it to print that input, however, it only prints the address of something on std::cout. When writing into a reference of a temporary object, Logger().stream(), works as expected.

Why is that happening?

Btw, this behavior only occurs in C++98-land (ideone), which I have to use. With C++11 (coliru) and C++14 (ideone) both call variants work as expected. What's different in C++11/14?

#include <iostream>
#include <sstream>

class Logger : public std::ostringstream
{
public:
    ~Logger()
    {
        std::cout << this->str() << std::endl;
    }

    Logger& stream()
    {
        return *this;
    }
};

int main( int argc, char ** argv )
{
    // 1.
    // Prints an address, e.g. 0x106e89d5c.
    Logger() << "foo";

    // 2.
    // Works as expected.
    Logger().stream() << "foo";

    // What is the difference between 1. and 2.?

    return 0;
}
nils
  • 2,424
  • 1
  • 17
  • 31

3 Answers3

19

The operator<< that handles insertion of const char * is a non-member template:

template< class Traits > 
basic_ostream<char,Traits>& operator<<(basic_ostream<char,Traits>& os, const char* s);

It takes its stream by non-const (lvalue) reference, which does not bind to temporaries.

In C++98/03, the best viable function is the member operator<<(const void *), which prints an address.

In C++11 and later, the library supplies a special operator<< for rvalue streams:

template< class CharT, class Traits, class T >
basic_ostream< CharT, Traits >& operator<<( basic_ostream<CharT,Traits>&& os, 
                                            const T& value );

which does os << value and returns os, essentially performing the output operation on an lvalue stream.

T.C.
  • 133,968
  • 17
  • 288
  • 421
11

Relevant facts:

  1. Logger() is an rvalue, but Logger().stream() is an lvalue.
  2. The operator<< that takes a pointer and prints its address is a member of ostream&, whereas the operator<< that takes a const char* and prints the string is a free function,

    template<class traits>
    basic_ostream<char,traits>& operator<<(basic_ostream<char,traits>& out,
    const char* s);
    

Note that the first argument is a non-const lvalue reference, so it can't bind to an rvalue. Therefore, if the stream is an rvalue, this overload isn't viable. Consequently the const char* is converted to const void* and its address is printed. When you use Logger().stream(), which is an lvalue, this overload wins and the string is printed.

In C++11, a new rvalue stream insertion operator is added:

template <class charT, class traits, class T>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>&& os, const T& x);

with the effect os << x. Now this overload wins in Logger() << "foo", and forwards the argument as though the stream were an lvalue. Then the free function previously given is called.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
5

C++11 added this overloaded of non-member operator<<:

template< class CharT, class Traits, class T >    
basic_ostream< CharT, Traits >& operator<<( basic_ostream<CharT,Traits>&& os,
                                            const T& value );

Now, the operator you think you're calling in the Logger() case is this one:

template< class Traits >
basic_ostream<char,Traits>& operator<<( basic_ostream<char,Traits>& os,  
                                        const char* s );

That works for the Logger().stream() case because that is an lvalue reference, but that doesn't work for Logger() << "foo" case. Logger() cannot bind to an lvalue reference. There, the only viable overload is the member operator<<:

basic_ostream& operator<<( const void* value );

which prints the address.

Barry
  • 286,269
  • 29
  • 621
  • 977