How to make map.emplace automatically choose constructor in c++17?

153 Views Asked by At

In c++17 first, I have a struct like

typedef struct ST_A{
    int a;
    string s;

    ST_A(const int a, const string&s) :a(a),s(s) {}
}st_A;

And Now I have a simple map and emplace twice in terms of cppreference

map<string, st_A> m1;
m1.emplace("c", 10, "c");                   ---> case 1
m1.emplace(std::piecewise_construct,        ---> case 2
        std::forward_as_tuple("c"),
        std::forward_as_tuple(10, "c"));

Case 2 works fine but case 1 compiles in error. So how to solve it in case 1 and make emplace as concise as possible?

What if the map is much more complex? Like map<string, deque<pair<int, st_A>>> m2, how to emplace it as concise as possible?

What if self-define struct has more constructors?Like

typedef struct ST_A{
    int a = 0;
    string s;

    ST_A(const int a) :a(a) {}
    ST_A(const string&s) :s(s) {}
    ST_A(const int a, const string&s) :a(a),s(s) {}
}st_A;

would it confuse the compiler if there actually is some consice way to emplace as the above link in cppreference.


UPDATE If my original map is map<string, deque<pair<int, st_A>>> m2, and now I want to add an element of pair{"c1", pair{1, st_A{10, "c"}}}.That is to say, after adding, m2 changes into map{{"c1",deque{pair{1,st_A{10, "c"}}}} with the form of insert codes is like

m2.insert({"c1", {{1, {10, "c"}}}});

but it may call too many ctor/copy ctor, if I want to write in a more efficient way in performance, how could it be?

1

There are 1 best solutions below

3
Quimby On

emplace() constructs std::pair<const Key,Value> from the passed arguments.

Without std::piecewise_construct, you have to pass exactly two arguments, one to construct the key, one to construct the value:

m1.emplace("c", st_A{10, "c"});  

Which defeats the purpose of "emplacing" since you are constructing the value ahead of the call.

My recommendation is to use try_emplace which has a more friendly API, especially if the key is simple and allows exactly what you want:

auto [it, inserted ] = m1.try_emplace(Key{key_args...}, value_args...);  
auto [it, inserted ] = m1.try_emplace("c", 10, "c");  

In general, the first argument constructs the key, the rest construct the value. Key ctor can thus be omitted if it accepts just one argument.

The return value indicates whether the element has been inserted or if there is already an element with the same key present. it returns basically m1.at("c").

The key is always constructed, the value arguments are only "consumed"(=moved from) if inserted==true. Otherwise they are untouched.

Example

For map<string, deque<pair<int, st_A>>> m2;, you could the the following:

m2["C"].emplace_back(1, st_A{10, "c"});

std::deque does not have a constructor that accepts a single element, aggregate initialization does not work either. Not that it would save any copies, std::deque::emplace_back achieves that already. So m2["C"] does the construction efficiently already.

If you want to also elide st_A temporary, you can again fallback to std::pair's std::piecewise_construct:

m2["C"].emplace_back(std::piecewise_construct, std::forward_as_tuple(1),
                     std::forward_as_tuple(10, "c"));