Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Move semantics & argument evaluation order

Considering the following:

std::string make_what_string( const std::string &id );  struct basic_foo {     basic_foo( std::string message, std::string id ); };  struct foo     : public basic_foo {     foo::foo( std::string id)         : basic_foo( make_what_string( id ), std::move( id ) ) // Is this valid?     {     } }; 

Because parameter evaluation order in C++ is unspecified, I'm wondering if the line

basic_foo( make_what_string( id ), std::move( id ) ) 

in above code is valid.

I know that std::move is nothing more than a cast, but when is the std::string move ctor executed? After all arguments have been evaluated and it's time to call the base constructor? Or is this done during evaluation of the parameters? In other words:

Does the compiler do this:

std::string &&tmp2 = std::move(id); std::string tmp1 = make_what_string(id); basic_foo(tmp1, tmp2); 

which is valid. Or this:

std::string tmp2 = std::move(id); std::string tmp1 = make_what_string(id); basic_foo(tmp1, tmp2); 

which is invalid. Note that in both cases the order is the "unexpected" one.

like image 605
Tom Moers Avatar asked Mar 28 '13 11:03

Tom Moers


People also ask

What is a move semantic?

Move semantics is a set of semantic rules and tools of the C++ language. It was designed to move objects, whose lifetime expires, instead of copying them. The data is transferred from one object to another. In most cases, the data transfer does not move this data physically in memory.

When should I use move semantics?

Move semantics allows you to avoid unnecessary copies when working with temporary objects that are about to evaporate, and whose resources can safely be taken from that temporary object and used by another.

How are Move semantics implemented?

Move semantics aim to avoid the copying of data from temporary objects by instead stealing the memory location of where the object resides. This behaviour is implemented through the use of a move constructor and move assignment operator that act only on rvalue references.

What does move () do in C++?

std::move in C++Moves the elements in the range [first,last] into the range beginning at result. The value of the elements in the [first,last] is transferred to the elements pointed by result. After the call, the elements in the range [first,last] are left in an unspecified but valid state.


2 Answers

See section 1.9:

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.

and

When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. [ Note: Value computations and side effects associated with different argument expressions are unsequenced. —end note ]

I think the problem is that it's not very clear whether the initialization of the parameters is considered a side effect associated with the argument expressions. However, it appears to be backed up by section 5.2.2:

The initialization and destruction of each parameter occurs within the context of the calling function.

And there's also a note in the same paragraph that makes it a little clearer:

When a function is called, each parameter (8.3.5) shall be initialized (8.5, 12.8, 12.1) with its corresponding argument. [ Note: Such initializations are indeterminately sequenced with respect to each other (1.9) — end note ]

So yes, the initialization of the arguments are indeterminately sequenced with respect to each other. The initializations might occur in either of these orders:

std::string message = make_what_string(id); std::string id = std::move( id );  std::string id = std::move( id ); std::string message = make_what_string(id); 

In the second case, make_what_string ends up working with a moved-from string.

So, even though std::move doesn't actually move anything, the important thing is that the actual moving is also unsequenced with respect to the other argument.

The definition of the move constructor of basic_string(basic_string&& str) states:

[...] str is left in a valid state with an unspecified value.

So you don't have undefined behaviour, you have unspecified behaviour.

like image 113
Joseph Mansfield Avatar answered Sep 20 '22 12:09

Joseph Mansfield


It's not really valid. The order of function argument evaluation is unspecified. In other words, you don't know whether the compiler will choose this sequence:

tmp1 = make_what_string(id); tmp2 = std::move(id); basic_foo(tmp1, tmp2); 

or this one:

tmp1 = std::move(id); tmp2 = make_what_string(id);  //id has already been moved from! basic_foo(tmp2, tmp1); 
like image 43
Angew is no longer proud of SO Avatar answered Sep 22 '22 12:09

Angew is no longer proud of SO