Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this an instance of a reference-to-non-const binding to a temporary?

Tags:

c++

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);

like image 725
Lightness Races in Orbit Avatar asked Jan 18 '13 07:01

Lightness Races in Orbit


1 Answers

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.

like image 149
Matthieu M. Avatar answered Sep 22 '22 11:09

Matthieu M.