如何在C++17中使`map.emplace`自动选择构造函数?

huangapple go评论60阅读模式
英文:

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

问题

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 concise 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?

英文:

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

得分: 5

emplace() 从传递的参数构造 std::pair<const Key, Value>

没有 std::piecewise_construct,你必须传递确切的两个参数,一个用于构造键,一个用于构造值:

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

这违背了"emplace"的目的,因为在调用之前已经构造了值。

我的建议是使用 try_emplace,它具有更友好的API,特别是如果键是简单的,可以实现你想要的效果:

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

通常情况下,第一个参数构造键,其余的构造值。如果 Key 构造函数接受一个参数,那么可以省略它。

返回值指示元素是否已被"inserted",或者是否已经存在具有相同键的元素。it 基本上返回 m1.at("c")

键始终会被构造,只有在 inserted==true 时,值参数才会被"consumed"(=moved from)。否则它们不会受影响。

示例

对于 map<string, deque<pair<int, st_A>>> m2;,你可以使用以下方式:

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

std::deque 没有接受单个元素的构造函数,聚合初始化也不起作用。不过它并不会保存任何副本,std::deque::emplace_back 已经实现了这一点。所以 m2["C"] 已经高效地完成了构造。

如果你想省略 st_A 的临时对象,可以再次使用 std::pairstd::piecewise_construct:

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

emplace() constructs std::pair&lt;const Key,Value&gt; 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(&quot;c&quot;, st_A{10, &quot;c&quot;});  

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(&quot;c&quot;, 10, &quot;c&quot;);  

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(&quot;c&quot;).

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

Example

For map&lt;string, deque&lt;pair&lt;int, st_A&gt;&gt;&gt; m2;, you could the the following:

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

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[&quot;C&quot;] 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[&quot;C&quot;].emplace_back(std::piecewise_construct, std::forward_as_tuple(1),
                     std::forward_as_tuple(10, &quot;c&quot;));

huangapple
  • 本文由 发表于 2023年2月6日 18:22:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/75360033.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定