接受右值引用的函数,如果未指定顺序,如何使用它两次

Function accepting rvalue reference, how to use it twice if order is unspecified

想象一个函数接受它可以移动的右值引用。如果该函数需要多次使用该对象,最后一次使用可以利用右值引用,并且可以 std::move 从那个。

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
}

但是如果操作顺序是不确定的顺序怎么办

  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.

例如

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

是使用至少一个移动的唯一选择,通过显式复制和移动两者作为

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
}

还是我遗漏了什么?

你的推理完全正确。

如果按照您描述的那样操作,就不会出现问题。但每个案例都是个别的。标准库的设计者预见到了这种情况,做了两次重载:

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)

在这种特殊情况下不需要显式复制,您可以毫无问题地调用

void foo(std::string&& str) {
    std::unordered_map<std::string, std::string> map;

    map.try_emplace(str, std::move(str)); // ok, (1) is used.
}

注意,str 是左值,是对右值的引用,std::move(str) 除了将左值类型转换为右值引用外,什么都不做。

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