{"id":110535,"date":"2024-11-18T07:00:00","date_gmt":"2024-11-18T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=110535"},"modified":"2024-11-18T09:48:36","modified_gmt":"2024-11-18T17:48:36","slug":"20241118-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20241118-00\/?p=110535","title":{"rendered":"The operations for reading and writing single elements for C++ standard library maps"},"content":{"rendered":"<p>Some time ago, I noted that <a title=\"The std::map subscript operator is a convenience, but a potentially dangerous one\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190227-00\/?p=101072\"> the <code>std::map<\/code> subscript operator is an attractive nuisance<\/a>. It is the most convenient syntax, but is not often what you actually want.<\/p>\n<p>I&#8217;ve broken down the various <code>std::map<\/code> lookup and update operations into a table so you can choose the best one for your situation.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Operation<\/th>\n<th>Method<\/th>\n<\/tr>\n<tr>\n<td>Read, throw if missing<\/td>\n<td><tt>m.at(key)<\/tt><\/td>\n<\/tr>\n<tr>\n<td>Read, allow missing<\/td>\n<td><tt>m.find(key)<\/tt><\/td>\n<\/tr>\n<tr>\n<td>Read, create if missing<\/td>\n<td><tt>m[key]<\/tt><\/td>\n<\/tr>\n<tr>\n<td>Write, nop if exists, discard value<\/td>\n<td><tt>m.insert({ key, value })<\/tt><br \/>\n<tt>m.emplace(key, value)<\/tt><\/td>\n<\/tr>\n<tr>\n<td>Write, nop if exists<\/td>\n<td><tt>m.emplace(<a title=\"What's up with std::piecewise_construct and std::forward_as_tuple?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220428-00\/?p=106540\">std::piecewise_construct<\/a>, ...)<\/tt><br \/>\n<tt>m.try_emplace(key, params)<\/tt><\/td>\n<\/tr>\n<tr>\n<td>Write, overwrite if exists<\/td>\n<td><tt>m.insert_or_assign(key, value)<\/tt><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>In the table above, <code>key<\/code> is the map key, <code>value<\/code> is the mapped type, and <code>params<\/code> are parameters to the mapped type constructor.<\/p>\n<p>Note that <code>insert<\/code> and the first <code>emplace<\/code>\u00b9 take a value which is discarded if it turns out that the key already exists. This is undesirable if creating the value is expensive.<\/p>\n<p>One frustrating scenario is the case where the mapped type&#8217;s default constructor is not the constructor you want to use for <code>operator[]<\/code>, or if you want the initial mapped value to be the result of a function call rather than a constructor. Here&#8217;s something I sort of threw together.<\/p>\n<pre>template&lt;typename Map, typename Key, typename Maker&gt;\r\nauto&amp; ensure(Map&amp;&amp; map, Key&amp;&amp; key, Maker&amp;&amp; maker)\r\n{\r\n    auto lower = map.lower_bound(key);\r\n    if (lower == map.end() || !map.key_comp()(lower-&gt;first, key)) {\r\n        lower = map.emplace_hint(lower, std::forward&lt;Key&gt;(key),\r\n                                 std::forward&lt;Maker&gt;(maker)());\r\n    }\r\n    return lower-&gt;second;\r\n}\r\n<\/pre>\n<p>This returns a reference to the mapped value that corresponds to the provided key, creating one if necessary via the <code>maker<\/code>. We forward the key into <code>emplace_<wbr \/>hint<\/code> so it can be moved.<\/p>\n<pre>struct Widget\r\n{\r\n    Widget(std::string name);\r\n\r\n    \u27e6 ... other stuff ... \u27e7\r\n};\r\nstd::map&lt;std::string, std::shared_ptr&lt;Widget&gt;&gt; widgets;\r\n\r\nauto&amp; ensure_named_widget(std::string const&amp; name)\r\n{\r\n    return ensure(widgets, name,\r\n        [&amp;] { return std::make_shared&lt;Widget&gt;(name); });\r\n}\r\n<\/pre>\n<p>As a convenience, we can accept bonus parameters which are passed to the <code>maker<\/code>. This allows you to parameterize the <code>maker<\/code>.<\/p>\n<pre>template&lt;typename Map, typename Key, typename... Maker&gt;\r\nauto&amp; ensure(Map&amp;&amp; map, Key&amp;&amp; key, Maker&amp;&amp;... maker)\r\n{\r\n    auto lower = map.lower_bound(key);\r\n    if (lower == map.end() || map.key_comp()(lower-&gt;first, key)) {\r\n        lower = map.emplace_hint(lower, std::forward&lt;Key&gt;(key),\r\n            std::invoke(std::forward&lt;Maker&gt;(maker)...));\r\n    }\r\n    return lower-&gt;second;\r\n}\r\n\r\nauto&amp; ensure_named_widget(std::string const&amp; name)\r\n{\r\n    return ensure(widgets, name,\r\n        [](auto&amp;&amp; name) { return std::make_shared&lt;Widget&gt;(name); },\r\n        name);\r\n}\r\n\r\n\/\/ or\r\n\r\nauto shared_widget_maker(std::string const&amp; name)\r\n{\r\n    return std::make_shared&lt;Widget&gt;(name);\r\n}\r\n\r\nauto&amp; ensure_named_widget(std::string const&amp; name)\r\n{\r\n    return ensure(widgets, name, shared_widget_maker, name);\r\n}\r\n<\/pre>\n<p>But wait, we learned last time that <a title=\"How do I put a non-copyable, non-movable, non-constructible object into a std::optional?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20241115-00\/?p=110527\"> conversion operators are in play<\/a>. We can use our <code>EmplaceHelper<\/code> in conjunction with <code>try_emplace<\/code>.<\/p>\n<pre>template&lt;typename Map, typename Key, typename... Maker&gt;\r\nauto&amp; ensure(Map&amp;&amp; map, Key&amp;&amp; key, Maker&amp;&amp;... maker)\r\n{\r\n    return *map.try_emplace(key, EmplaceHelper([&amp;] {\r\n        return std::invoke(std::forward&lt;Maker&gt;(maker)...);\r\n    }).first;\r\n}\r\n<\/pre>\n<p>The <code>try_emplace<\/code> function returns a <code>std::pair<\/code> of an iterator to the matching item (either pre-existing or freshly-created) and a <code>bool<\/code> that indicates whether the item is freshly-created. We don&#8217;t care about whether the item is freshly-created, so we just take the iterator (the <code>.first<\/code>) and dereference it to get a reference to the item.<\/p>\n<p>This is simple enough that you might just write it out rather than calling out to a one-liner helper.<\/p>\n<pre>auto&amp; item =\r\n    *widgets.try_emplace(name, EmplaceHelper([&amp;] {\r\n        return std::make_shared&lt;Widget&gt;(name); }).first;\r\n\r\n\/\/ or\r\n\r\nauto&amp; item =\r\n    *widgets.try_emplace(name,\r\n        EmplaceHelper(shared_widget_maker, name)).first;\r\n<\/pre>\n<p>\u00b9 Technically, you can pass the first <code>emplace<\/code> a second parameter that is used to <i>construct<\/i> the <code>value<\/code>, so if your mapped type has a single-parameter constructor, you can use the first <code>emplace<\/code> to get the same effect as the piecewise constructor: on-demand construction of the mapped type.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Breaking down the options.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-110535","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Breaking down the options.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110535","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=110535"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110535\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=110535"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=110535"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=110535"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}