Home > database >  C : insert element into std::map<MyStruct> where MyStruct can only be aggregate initialized a
C : insert element into std::map<MyStruct> where MyStruct can only be aggregate initialized a

Time:03-16

Here is what I am trying to do and what I have tried:

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

I understand why none of this works but is there any way to insert an element into myMap without modifying MyStruct?

CodePudding user response:

Both

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

and

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

should work in C 20. You need to construct the pair in-place, because it will be non-copyable and non-movable due to MyStruct. This leaves only the emplace family of member functions. Using a braced initializer list in emplace can never work because emplace only forwards arguments, but wouldn't be able to deduce types for arguments.

Before C 20, these member functions also won't work, because they internally use direct-initialization with parentheses. MyStruct however has no constructor matching the two arguments. In C 20 parenthesized initializers can also do aggregate initialization and that will work, since MyStruct is an aggregate.


I don't think there is any possibility to add an element to the map before C 20 without change of MyStruct or adding an implicit conversion for it, because it can only be aggregate-initialized, but must be emplace constructed which doesn't support aggregate-initialization.


The only the exception to that should be default initialization. For example

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

should work also before C 20, because MyStruct is default-constructible.


An implicit conversion could be added to do aggregate initialization, for example:

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

This will probably work in C 17 mode on compilers, although it is technically not correct as mandatory copy elision doesn't apply to initialization by conversion operator. See CWG 2327.


As for changes of MyStruct: In particular the two examples quoted above should also work pre-C 20 if you add a matching constructor:

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

CodePudding user response:

Move semantics dont work with constant data.

And you need overload ur struct operators. https://en.cppreference.com/w/cpp/language/rule_of_three

  • Related