Home > Back-end >  Function accepting rvalue reference, how to use it twice if order is unspecified
Function accepting rvalue reference, how to use it twice if order is unspecified

Time:11-10

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

  1. 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.

Notes

Unlike insert or emplace, 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 as std::map<std::string, std::unique_ptr<foo>>. In addition, try_emplace treats the key and the arguments to the mapped_type separately, unlike emplace, which requires the arguments to construct a value_type (that is, a std::pair).

  • Related