Imagine a function accepting a rvalue reference that it can move from. If the function needs to use that object multiple times, the last usage can take advantage of the rvalue reference and can std::move
from that.
void tripple(std::string&& str) {
std::vector<std::string> vector;
vector.push_back(str); // invokes 'const std::string&', will copy
vector.push_back(str); // invokes 'const std::string&', will copy
vector.push_back(std::move(str)); // invokes 'std::string&&', will move
}
But what if the order of operation is indeterminately sequenced
- In a function call, value computations and side effects of the initialization of every parameter are indeterminately sequenced with respect to value computations and side effects of any other parameter.
So for example
void foo(std::string&& str) {
std::unordered_map<std::string, std::string> map;
map.try_emplace(str, str); // will copy key and emplace the value from copy of str
// any of those three shouldn't be valid
map.try_emplace(std::move(str), str);
map.try_emplace(str, std::move(str));
map.try_emplace(std::move(str), std::move(str));
}
Is the only option to use at least one move by explicitly making a copy and moving both as
void foo(std::string&& str) {
std::unordered_map<std::string, std::string> map;
std::string copy = str;
map.try_emplace(std::move(copy), std::move(str)); // ok, two moves
}
or am I missing something?
CodePudding user response:
Your reasoning is absolutely correct.
If you do it like you described it, you will not get issues. But every case is individual. The designers of the standard library foreseen this case and made two overloads:
template< class... Args >
pair<iterator, bool> try_emplace( const Key& k, Args&&... args ); // (1)
template< class... Args >
pair<iterator, bool> try_emplace( Key&& k, Args&&... args ); // (2)
In this particular case no explicit copy is required, you can call without any issue
void foo(std::string&& str) {
std::unordered_map<std::string, std::string> map;
map.try_emplace(str, std::move(str)); // ok, (1) is used.
}
Note, str
is l-value, is reference to r-value, std::move(str)
performs nothing except typecasting l-value to r-value reference.
Unlike
insert
oremplace
, these functions do not move from rvalue arguments if the insertion does not happen, which makes it easy to manipulate maps whose values are move-only types, such asstd::map<std::string, std::unique_ptr<foo>>
. In addition,try_emplace
treats the key and the arguments to themapped_type
separately, unlikeemplace
, which requires the arguments to construct avalue_type
(that is, astd::pair
).