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