5

Disclaimer: This question is for understanding. I'll use boost::lexical_cast in the field. It has sort of come up in the real world in places, though.


Take the following attempt at an "inline" lex-cast approach:

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

int main()
{
    const std::string s = static_cast<std::ostringstream&>(
       std::ostringstream() << "hi" << 0
    ).str();
    std::cout << s;
}

The result is something like 0x804947c0, because the operator<< that works with "hi" is a free function whose LHS must take std::ostream&, and temporary std::ostringstream() can't bind to a ref-to-non-const. The only remaining match is the operator<< that takes const void* on the RHS††.

Now let's swap the operands:

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

int main()
{
    const std::string s = static_cast<std::ostringstream&>(
       std::ostringstream() << 0 << "hi"
    ).str();
    std::cout << s;
}

The result is "0hi".

This mostly makes sense, because the operator<< that takes int is a member function of base ostream††† and, as such, is fine with being invoked on the temporary. The result of that operation is a reference to the ostream base, to which the next operator<< is chained, i.e. read it as (std::ostringstream() << 0) << "hi".

But why then does that operation on "hi" go on to yield the expected result? Isn't the reference on the LHS still a temporary?


Let's focus on C++03; I'm told that the first example may actually work as "intended" in C++11 due to the catch-all operator for rvalues.

[C++03: 27.6.2.1]: template<class charT, class traits> basic_ostream<charT,traits>& operator<<(basic_ostream<charT,traits>&,charT*);

†† [C++03: 27.6.2.1]: basic_ostream<charT,traits>& operator<<(const void* p);

††† [C++03: 27.6.2.1]: basic_ostream<charT,traits>& operator<<(int n);

Community
  • 1
  • 1
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • I guess the answer is something along the lines of "the rules for binding temporaries to references are nowhere near that simple; here are the pertinent phrases". – Lightness Races in Orbit Jan 18 '13 at 07:06
  • This works for the same reason that you can write a function template `template T& lvalue(T&& v) {return v;}` in order to bind non-const lvalue references to temporaries. – Mankarse Jan 18 '13 at 07:14
  • Wouldn't the overloads for your first footnote be the ones that take `const char[T]*`? (Not that it changes anything.) – Mat Jan 18 '13 at 07:15
  • @Mankarse: Oops - this should all be about C++03. – Lightness Races in Orbit Jan 18 '13 at 07:15
  • @Mat: I'm not aware that any exist? And certainly not as members, which is the point here. :) – Lightness Races in Orbit Jan 18 '13 at 07:17
  • The same principle applies in C++03. You can write a member function `T& ref() {return *this;}` which can called on temporaries to allow the creation of lvalue references to them. (It is only the expression that results in the temporary that is an `rvalue`, `lvalues` to the same object can still be created). – Mankarse Jan 18 '13 at 07:21
  • Possible duplicate of [std::ostringstream printing the address of the c-string instead of its content](http://stackoverflow.com/questions/8287188/stdostringstream-printing-the-address-of-the-c-string-instead-of-its-content). Got caught by this a year ago or so... stupid non-binding rule :( – Matthieu M. Jan 18 '13 at 10:02
  • @Mankarse: I think the last sentence of your previous comment is the answer that I've been looking for – Lightness Races in Orbit Jan 18 '13 at 17:51
  • @MatthieuM.: It's a kind of reverse duplicate, in that I'm well aware of the reason for the "address gets printed behaviour", but in this instance I'm keen to discover why it _doesn't_ happen. – Lightness Races in Orbit Jan 18 '13 at 17:52
  • @LightnessRacesinOrbit: Sorry, I thought it was obvious from Nawaz' answer. I detailed the reason in a proper answer below. – Matthieu M. Jan 18 '13 at 18:35

2 Answers2

3

The reason is simple. If you read the question I asked about:

std::ostringstream printing the address of the c-string instead of its content.

you will note that the trick to getting a "proper" reference instead of a temporary is to call a method on the object (not restricted to the not binding restriction for some reason) that will return a reference.

In Nawaz's answer above, he called std::ostream& std::ostream::flush(), in your case here:

std::ostringstream() << 0 << "hi"

you call std::ostringstream& std::ostringstream::operator<<(int).

Same result.

The surprising behavior is due to ostream mishmash implementations: some operator<< are member methods while others are free-functions.

You can test it, simply, by implementing an X& ref() method on an object:

struct X { X& ref(); };

void call(X& x);

int main() {
    call(X{});       // error: cannot bind X& to a temporary
    call(X{}.ref()); // OK
}

EDIT: but why is not X& (the result of ref) treated the same ?

It is a matter of classification. A temporary is a prvalue whilst a reference is a lvalue. A reference is only allowed to bind to a lvalue.

Of course since methods can be called on rvalue (and thus prvalue) and those methods may return a reference to the objects they were called on we can easily bypass the silly (1) a reference is only allowed to bind to a lvalue restriction...

(1) it's also inconsistent with the fact that a rvalue can be bound to a const-reference.

Community
  • 1
  • 1
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 1
    I don't feel that this answers the question. I know about the member function and the free function. But the object is still a temporary, so why does the reference bind to it in the end just because it has been produced by a function call? – Lightness Races in Orbit Jan 18 '13 at 18:36
  • @LightnessRacesinOrbit: a simple `lvalue`/`rvalue` issue, I extended my answer. How could the compiler know that `X& X::ref()` is the one `X` instance that `ref` is called on ? It cannot (in general), so it's purely based on classification of types. – Matthieu M. Jan 18 '13 at 20:53
2

Isn't the reference on the LHS still a temporary?

It is a lvalue reference to a temporary (the return value), which is still a lvalue and so can bind to lvalue references.

Think of temporaries in terms of lifetime rather than l/rvalueness.

Pubby
  • 51,882
  • 13
  • 139
  • 180