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 ?
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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With