20

I have stumbled on a weird behavior that I just could not explain at first (see ideone):

#include <iostream>
#include <sstream>
#include <string>

int main() {
  std::cout << "Reference     : "
            << (void const*)"some data"
            << "\n";

  std::ostringstream s;
  s << "some data";
  std::cout << "Regular Syntax: " << s.str() << "\n";

  std::ostringstream s2;
  std::cout << "Semi inline   : "
            << static_cast<std::ostringstream&>(s2 << "some data").str()
            << "\n";

  std::cout << "Inline        : "
            << dynamic_cast<std::ostringstream&>(
                 std::ostringstream() << "some data"
               ).str()
            << "\n";
}

Gives the output:

Reference     : 0x804a03d
Regular Syntax: some data
Semi inline   : some data
Inline        : 0x804a03d

Surprisingly, in the last cast we have the address, and not the content!

Why is that so ?

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722

3 Answers3

20

The expressionstd::ostringstream() creates a temporary, and operator<< which takes const char* as argument is a free function, but this free function cannot be called on a temporary, as the type of the first parameter of the function is std::ostream& which cannot be bound to temporary object.

Having said that, <<std::ostringstream() << "some data" resolves to a call to a member function which is overloaded for void* which prints the address. Note that a member function can be invoked on the temporary.

In order to call the free function, you need to convert temporary (which is rvalue) into a lvalue, and here is one trick that you can do:

 std::cout << "Inline        : "
            << dynamic_cast<std::ostringstream&>(
                 std::ostringstream().flush() << "some data"
               ).str()
            << "\n";

That is, std::ostringstream().flush() returns std::ostream& which means, now the free function can called, passing the returned reference as first argument.

Also, you don't need to use dynamic_cast here (which is slow, as it is done at runtime), for the type of the object is pretty much known, and so you can use static_cast (which is fast as it is done at compile-time):

 std::cout << "Inline        : "
            << static_cast<std::ostringstream&>(
                 std::ostringstream().flush() << "some data"
               ).str()
            << "\n";

which should work just fine.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 2
    Exact! Took me an hour to figure it out :x I've posted my investigation below... and somehow wish I had ask earlier (though it would not have been as educative). – Matthieu M. Nov 27 '11 at 16:57
  • 3
    Wow! Your workaround is neater than mine. Definitely one of those days when I want to bash Stroustrup on the head for ruling out binding to non-const :x – Matthieu M. Nov 27 '11 at 17:03
  • @MatthieuM. : Kerrek and I had a small discussion on this. If you're interested, [read the comments here](http://stackoverflow.com/questions/8116808/read-integers-from-a-text-file-with-c-ifstream/8116840#comment9981318_8116840) (the comments on my answer). There are other work around as well, but using `flush` is better than the rest. – Nawaz Nov 27 '11 at 17:05
  • I don't get this line *as the type of the first parameter of the function is std::ostream& which cannot be bound to temporary object.* . Can you express the sentence in easy words? – Mr.Anubis Dec 07 '11 at 21:23
  • 2
    @FreakEnum: You **cannot** write this : `A & a = A()`, as the expression `A()` creates a temporary object of type `A`, but non-const reference (on the left-side of assignment) cannot be bound to the temporary, according to the language specification. But once you make it a const reference, then the binding is possible; that is, you **can** write this : `A const & a = A()`. Similarly, `std::ostream&` is a non-const reference which cannot be bound to the temporary object created out of the expression `std::ostringstream()`. – Nawaz Dec 08 '11 at 04:14
  • @FreakEnum: For more detail, you can read this topic : [Temporary non-const istream reference in constructor (C++)](http://stackoverflow.com/questions/2405871/temporary-non-const-istream-reference-in-constructor-c) – Nawaz Dec 08 '11 at 04:15
9

A temporary cannot bind to a reference to non-const formal argument.

Therefore, the non-member << is not picked up.

You get the void* version instead.

C++11 fixes this by adding a non-member rvalue stream inserter function,

C++11
§27.7.3.9 Rvalue stream insertion
[ostream.rvalue]
template <class charT, class traits, class T>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>&& os, const T& x);

1 Effects: os << x
2 Returns: os

halfer
  • 19,824
  • 17
  • 99
  • 186
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • Ah thanks for this, I didn't know they had solved the issue in C++11. It's incredible how adding the rvalue references/move semantics simplifies life. – Matthieu M. Nov 27 '11 at 17:30
  • @Matthieu: Sadly, [other subtle issues](http://stackoverflow.com/q/8828973/500104) crept in because of these, and it seems there is no really satisfactory solution for these nasty temporary streams. – Xeo Mar 09 '12 at 08:17
  • @Xeo: yes, already saw (and upvoted) these :) I really like the defect Howard linked to (well, its proposed resolution at least). – Matthieu M. Mar 09 '12 at 08:24
  • add a reference that contains discussion [1203. More useful rvalue stream insertion](https://cplusplus.github.io/LWG/issue1203) – samm Nov 02 '21 at 04:03
4

To get the started, the simplest solution is to get the list of possible overloads that the compiler considered, for example trying this:

X x;
std::cout << x << "\n";

where X is a type without any overload for streaming which yields the following list of possible overloads:

prog.cpp: In function ‘int main()’:
prog.cpp:21: error: no match for ‘operator<<’ in ‘std::cout << x’
include/ostream:112: note: candidates are: std::ostream& std::ostream::operator<<(std::ostream& (*)(std::ostream&))
include/ostream:121: note:                 std::ostream& std::ostream::operator<<(std::basic_ios<_CharT, _Traits>& (*)(std::basic_ios<_CharT, _Traits>&))
include/ostream:131: note:                 std::ostream& std::ostream::operator<<(std::ios_base& (*)(std::ios_base&))
include/ostream:169: note:                 std::ostream& std::ostream::operator<<(long int)
include/ostream:173: note:                 std::ostream& std::ostream::operator<<(long unsigned int)
include/ostream:177: note:                 std::ostream& std::ostream::operator<<(bool)
include/bits/ostream.tcc:97: note:         std::ostream& std::ostream::operator<<(short int)
include/ostream:184: note:                 std::ostream& std::ostream::operator<<(short unsigned int)
include/bits/ostream.tcc:111: note:        std::ostream& std::ostream::operator<<(int)
include/ostream:195: note:                 std::ostream& std::ostream::operator<<(unsigned int)
include/ostream:204: note:                 std::ostream& std::ostream::operator<<(long long int)
include/ostream:208: note:                 std::ostream& std::ostream::operator<<(long long unsigned int)
include/ostream:213: note:                 std::ostream& std::ostream::operator<<(double)
include/ostream:217: note:                 std::ostream& std::ostream::operator<<(float)
include/ostream:225: note:                 std::ostream& std::ostream::operator<<(long double)
include/ostream:229: note:                 std::ostream& std::ostream::operator<<(const void*)
include/bits/ostream.tcc:125: note:        std::ostream& std::ostream::operator<<(std::basic_streambuf<_CharT, _Traits>*)

First scanning this list, we can remark that char const* is conspiscuously absent, and therefore it is logical that void const* will be selected instead and thus the address printed.

On a second glance, we note that all overloads are methods, and that not a single free function appears here.

The issue is a problem of reference binding: because a temporary cannot bind to a reference to non-const, overloads of the form std::ostream& operator<<(std::ostream&,X) are rejected outright and only member functions remain.

It is, as far as I am concerned, a design bug in C++, after all we are executing a mutating member function on a temporary, and this requires a (hidden) reference to the object :x

The workaround, once you understood what went awry, is relatively simple and only requires a small wrapper:

struct Streamliner {
  template <typename T>
  Streamliner& operator<<(T const& t) {
    _stream << t;
    return *this;
  }

  std::string str() const { return _stream.str(); }
  std::ostringstream _stream;
};

std::cout << "Inline, take 2: " << (Streamliner() << "some data").str() << "\n";

Which prints the expected result.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722