C++:将元素插入 std::map<MyStruct>,其中 MyStruct 只能进行聚合初始化并包含常量唯一指针

C++: insert element into std::map<MyStruct> where MyStruct can only be aggregate initialized and contains const unique pointers

这是我正在尝试做的以及我已经尝试过的事情:

#include <memory>
#include <map>
#include <string>

using namespace std;

struct MyStruct {
    const unique_ptr<int> a;
    const unique_ptr<int> b;
};

int main() {
    auto a = make_unique<int>(7);
    auto b = make_unique<int>(5);

    map<string, MyStruct> myMap;

    myMap["ab"] = { move(a), move(b) }; // nope
    myMap.insert("ab", { move(a), move(b) }); // nope
    myMap.emplace("ab", { move(a), move(b) }); // nope
    myMap.try_emplace("ab", { move(a), move(b) }); // nope

    // EDIT 1:

    myMap.emplace(piecewise_construct,
        forward_as_tuple("ab"),
        forward_as_tuple(move(a), move(b))
    ); // nope

    // EDIT 2:

    // works in C++20 as there is now a standard ctor for MyStruct:
    myMap.try_emplace("ab", move(a), move(b));

    return 0;
}

我明白为什么 none 这行得通,但是有什么方法可以在不修改 MyStruct 的情况下将元素插入到 myMap 中吗?

移动语义不适用于常量数据。

并且您需要重载您的结构运算符。 https://en.cppreference.com/w/cpp/language/rule_of_three

两者都

myMap.emplace(piecewise_construct,
    forward_as_tuple("ab"),
    forward_as_tuple(move(a), move(b))
);

myMap.try_emplace("ab", move(a), move(b));

应该在 C++20 中工作。您需要构建 in-place 对,因为 non-copyable 和 non-movable 由于 MyStruct。这只剩下 emplace 族成员函数。在 emplace 中使用花括号初始化器列表永远行不通,因为 emplace 只能转发参数,但无法推断参数的类型。

在C++20之前,这些成员函数也不起作用,因为它们在内部使用带括号的direct-initialization。 MyStruct 但是没有匹配这两个参数的构造函数。在 C++20 中,带括号的初始化器也可以进行聚合初始化,这会起作用,因为 MyStruct 是一个聚合。


我认为在不改变 MyStruct 或为其添加隐式转换的情况下,不可能在 C++20 之前向映射添加元素,因为它只能是 aggregate-initialized, 但必须 emplace 构造不支持 aggregate-initialization.


唯一的例外应该是默认初始化。例如

myMap.emplace(piecewise_construct,
    forward_as_tuple("ab"),
    forward_as_tuple()
);

也应该在 C++20 之前工作,因为 MyStruct 是 default-constructible.


可以添加隐式转换来进行聚合初始化,例如:

struct A {
    std::unique_ptr<int> a;
    std::unique_ptr<int> b;
    operator MyStruct() && {
        return {std::move(a), std::move(b)};
    }
};

//...

myMap.emplace("ab", A{ std::move(a), std::move(b) });

这可能会在编译器的 C++17 模式下工作,尽管它在技术上是不正确的,因为强制复制省略不适用于转换运算符的初始化。参见 CWG 2327


至于 MyStruct 的变化:特别是上面引用的两个例子也应该工作 pre-C++20 如果你添加一个匹配的构造函数:

struct MyStruct {
    MyStruct(std::unique_ptr<int> a_, std::unique_ptr<int> b_) : a(std::move(a_)), b(std::move(b_)) { }

    const std::unique_ptr<int> a;
    const std::unique_ptr<int> b;
};