Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

xvalue's lifetime bound to reference extended or not?

Apparently there is some confusion and differences between compilers as regards this issue:

http://social.msdn.microsoft.com/Forums/vstudio/en-US/3c754c4e-5471-4095-afae-795c1f411612/rvalue-refs-extended-lifetime-inconsistent-with-gccstandard

According to this post:

What are rvalues, lvalues, xvalues, glvalues, and prvalues?

Xvalues are rvalues (along with prvalues) and the standard says:

The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:

However there exist posts that dispute this:

Do rvalue references allow dangling references?

What is an example of a difference in allowed usage or behavior between an xvalue and a prvalue FOR NON-POD objects?

Can someone please clarify the issue. Is MSVC right for once?

like image 845
user1095108 Avatar asked Jul 21 '13 03:07

user1095108


1 Answers

Xvalues may be rvalues, but that doesn't mean that they are temporaries. The lifetime extension of temporaries comes from the fact that they're temporaries, not their value category.

I deliberately try not to know the order in which operators are processed (that way, I force myself to write code that either uses explicit parentheses or doesn't care about the order). Your specific adder example code, reproduced here, does care:

template <class T>
struct addable
{
    friend T operator +( const T& lhs, const T& rhs )
    {
        return std::move(T(lhs) += rhs); 
    }
    friend T operator +( const T& lhs, T&& rhs )
    {
        return std::move(T(lhs) += std::move(rhs));
    }   
    friend T&& operator +( T&& lhs, const T& rhs ) 
    {
        return std::move(lhs += rhs);
    }               
    friend T&& operator +( T&& lhs, T&& rhs ) 
    {
        return std::move(lhs += std::move(rhs)); 
    }
};

If the + operator is done right-to-left, then t1 + t2 + t3 will work out to t1 + (t2 + t3). t2 + t3 will call the first overload, thus producing a temporary, thus yielding t1 + temp. Since a temporary will preferentially bind to an r-value reference, that expression will call the second overload, which will also return a temporary.

However, if the + operator works left-to-right, then you get (t1 + t2) + t3. This gives us temp + t1, which causes a problem. It will call the third overload. The lhs parameter of that function is a T&&, a reference to the temporary. You return the same reference. Which means you have returned a reference to a temporary. But C++ doesn't know that; all it knows is that you're returning a reference to something.

That "something" however is due to be destroyed after the final expression (the assignment to the new variable, either a value type or a reference type) has been evaluated. Remember: C++ doesn't know that this function will return a reference to it's first parameter. So it has no way of knowing that the lifetime of a temporary passed to a function operand needs to be extended to the lifetime where the returned reference is stored.

Incidentally, this is why expression trees can be dangerous with auto and such lurking about. Because the inner temporaries created cannot be preserved by new temporaries or references stored in various objects. C++ just doesn't have a way to do that.

So who's right depends on which order the operators are resolved in. However, I prefer my solution: don't rely on these corners of the language and just work around them. Stop returning T&& from these overloads and just move the value to a temporary. That way, it's guaranteed to work correctly and you don't have to constantly check the standard to make sure your code is working.

Also, as an aside, I would consider it somewhat rude for operator+ to actually modify one of the parameters.

However, if you insist on knowing who's right, it's GCC. From section 5.7, p1:

The additive operators + and - group left-to-right.

So yes, it shouldn't work.

Note: Visual Studio allows T &r2 = t1 + t2 + t3; to compile as a (highly annoying) language extension. You should hopefully have gotten a warning from it.

like image 89
Nicol Bolas Avatar answered Sep 28 '22 02:09

Nicol Bolas