C++ 标准是否保证插入关联容器失败不会修改右值引用参数?

Does the C++ standard guarantee that a failed insertion into an associative container will not modify the rvalue-reference argument?

#include <set>
#include <string>
#include <cassert>

using namespace std::literals;

int main()
{
    auto coll = std::set{ "hello"s };
    auto s = "hello"s;
    coll.insert(std::move(s));
    assert("hello"s == s); // Always OK?
}

C++ 标准是否保证插入关联容器失败不会修改右值引用参数?

没有

虽然@NathanOliver指出当且仅当没有等效键时才不会插入元素,但并不保证参数不会被修改。

其实[map.modifiers]说的是下面

template <class P>
pair<iterator, bool> insert(P&& x);

equivalent to return emplace(std::forward<P>(x)).

其中 emplace 可以完美地转发参数以构建另一个 P,使 x 处于某个有效但不确定的状态。

这是一个示例,它也演示(不是证明)使用 std::map(关联容器),值会移动一点:

#include <iostream>
#include <utility>
#include <string>
#include <map>

struct my_class
{
    my_class() = default;
    my_class(my_class&& other)
    {
        std::cout << "move constructing my_class\n";
        val = other.val;
    }
    my_class(const my_class& other)
    {
        std::cout << "copy constructing my_class\n";
        val = other.val;
    }
    my_class& operator=(const my_class& other)
    {
        std::cout << "copy assigning my_class\n";
        val = other.val;
        return *this;
    }
    my_class& operator=(my_class& other)
    {
        std::cout << "move assigning my_class\n";
        val = other.val;
        return *this;
    }
    bool operator<(const my_class& other) const
    {
        return val < other.val;
    }
    int val = 0;
};

int main()
{
    std::map<my_class, int> my_map;
    my_class a;
    my_map[a] = 1;
    std::pair<my_class, int> b = std::make_pair(my_class{}, 2);
    my_map.insert(std::move(b)); // will print that the move ctor was called
}

明确且明确。标准没有这种保证,这就是 try_emplace 存在的原因。

见注释:

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)

(仅针对 C++17 的答案)

我认为正确答案介于 NathanOliver(现已删除)的答案和 AndyG 的答案之间。

正如 AndyG 指出的那样,这样的保证 cannot 通常存在:有时,库 必须 实际上执行移动构造只是为了确定插入是否可以发生。 emplace 函数就是这种情况,其行为由标准指定为:

Effects: Inserts a value_type object t constructed with std::forward<Args>(args)... if and only if there is no element in the container with key equivalent to the key of t.

我们可以将其解释为对象 t 无论如何都会被构造,然后如果因为值 tt.first 已经无法插入而被丢弃分别存在于集合或映射中。由于 std::map 的方法 template <class P> pair<iterator, bool> insert(P&&) 是根据 emplace 指定的,正如 AndyG 指出的那样,它具有相同的行为。正如 SergeyA 指出的那样,try_emplace 方法旨在避免此问题。

然而,在OP给出的具体例子中,插入的值与容器的值类型完全相同。这种 insert 调用的行为由 NathanOliver 先前给出的一般要求段落指定:

Effects: Inserts t if and only if there is no element in the container with key equivalent to the key of t.

在这种情况下,没有许可库在插入未发生的情况下修改参数。我相信除了标准明确允许的之外,调用库函数不应该有任何可观察到的副作用。因此,这种情况下,t 一定不能修改。