{"id":655,"date":"2017-11-18T01:00:39","date_gmt":"2017-11-18T09:00:39","guid":{"rendered":"http:\/\/wonghoi.humgar.com\/blog\/?p=655"},"modified":"2017-11-22T00:26:39","modified_gmt":"2017-11-22T08:26:39","slug":"c-annoyances-and-reliefs-operator-in-stl-map-based-containers","status":"publish","type":"post","link":"https:\/\/wonghoi.humgar.com\/blog\/2017\/11\/18\/c-annoyances-and-reliefs-operator-in-stl-map-based-containers\/","title":{"rendered":"C++ annoyances (and reliefs): operator[] in STL map-based containers"},"content":{"rendered":"<p>I recently watched Louis Brandy&#8217;s CppCon presentation \u201c<a href=\"https:\/\/www.youtube.com\/watch?v=lkgszkPnV8g\">Curiously Recurring C++ Bugs at Facebook<\/a>\u201d on youtube.<\/p>\n<p><iframe loading=\"lazy\" title=\"CppCon 2017: Louis Brandy \u201cCuriously Recurring C++ Bugs at Facebook\u201d\" width=\"584\" height=\"329\" src=\"https:\/\/www.youtube.com\/embed\/lkgszkPnV8g?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<p>For\u00a0<a href=\"https:\/\/youtu.be\/lkgszkPnV8g?t=437\">bug#2<\/a>, which is a well-known trap for STL map-based containers, <span style=\"font-family: 'courier new', courier, monospace;\">operator[]<\/span> will insert the requested key (associated with a default-constructed value) if it is not found.\u00a0<\/p>\n<p>He mentioned a few workarounds and their disadvantages, like<\/p>\n<ul>\n<li>use <span style=\"font-family: 'courier new', courier, monospace;\">at()<\/span> method: requires exception handling<\/li>\n<li>const protect: noobs try to defeat that, transferred to non-const (stripped)<\/li>\n<li>ban <span style=\"font-family: 'courier new', courier, monospace;\">operator[]<\/span> calls: makes the code ugly<\/li>\n<\/ul>\n<p>but would like to see something neater. In <a href=\"https:\/\/youtu.be\/lkgszkPnV8g?t=787\">bug#3<\/a>, he added that a very common usage is to return a default when the key is not found. The normal approach requires returning a copy of the default (expensive if it&#8217;s large), which tempts noobs to return a local reference (to destroyed temporary variables: guaranteed bug).<\/p>\n<hr \/>\n<p>Considering how much productivity drain a clumsy interface can cause, I think it&#8217;s worth spending a few hours of my time approaching it, since I might need to use STL map-based containers myself someday.<\/p>\n<p>Here&#8217;s my thought process for the design choices:<\/p>\n<ul>\n<li>Retain the complete STL interface to minimize user code\/documentation changes<\/li>\n<li>Endow a STL map-based container with a <span style=\"font-family: 'courier new', courier, monospace;\">default_value<\/span> (common use case), so that the new <span style=\"font-family: 'courier new', courier, monospace;\">operator[]<\/span> can return a reference without worrying about temporaries getting destroyed.<\/li>\n<li>Give users a easy read-only access interface (make intentions clear with little typing)<\/li>\n<\/ul>\n<p>The code (with detailed comment about design decisions and test cases) can be downloaded here: <a href=\"https:\/\/wonghoi.humgar.com\/blog\/wp-content\/uploads\/2017\/11\/MapWithDefault.zip\">MapWithDefault<\/a>. For the experienced, here&#8217;s the meat:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\">#include &lt;unordered_map&gt;\r\n#include &lt;map&gt;\r\n\r\n#include &lt;utility&gt;  \/\/ std::forward\r\n\r\n\/\/ Legend (for extremely simple generic functions)\r\n\/\/ ===============================================\r\n\/\/ K: key\r\n\/\/ V: value\r\n\/\/ C: container\r\n\/\/ B: base (class)\r\ntemplate &lt;typename K, typename V, template &lt;typename ... Args&gt; class C = std::map, typename B = C&lt;K,V&gt; &gt;\r\nclass MapWithDefault : private B \r\n{\r\npublic:\r\n    \/\/ Make default_value mandatory. Everything else follows the requested STL container\r\n    template&lt;typename... Args&gt;\r\n    MapWithDefault(V default_value, Args&amp;&amp; ... args) : B(std::forward&lt;Args&gt;(args)...), default_value(default_value) {};\r\n\r\npublic:\r\n    using B::operator=;\r\n    using B::get_allocator;\r\n\r\n    using B::at;\r\n\r\n    using B::operator[];\r\n\r\n    \/\/ Read-only map (const object) uses only read-only operator[]\r\n    const V&amp; operator[](const K&amp; key) const\r\n    {\r\n        auto it = this-&gt;find(key);\r\n        return (it==this-&gt;end()) ? default_value : it-&gt;second;\r\n    }\r\n\r\n    using B::begin;\r\n    using B::cbegin;\r\n    using B::end;\r\n    using B::cend;\r\n    using B::rbegin;\r\n    using B::crbegin;\r\n    using B::rend;\r\n    using B::crend;\r\n\r\n    using B::empty;\r\n    using B::size;\r\n    using B::max_size;\r\n\r\n    using B::clear;\r\n    using B::insert;\r\n    \/\/ using B::insert_or_assign;   \/\/ C++17\r\n    using B::emplace;\r\n    using B::emplace_hint;\r\n    using B::erase;\r\n    using B::swap;\r\n\r\n    using B::count;\r\n    using B::find;\r\n    using B::equal_range;\r\n    using B::lower_bound;\r\n    using B::upper_bound;\r\n\r\npublic:\r\n    const               V default_value;\r\n    const MapWithDefault&amp; read_only = static_cast&lt;MapWithDefault&amp;&gt;(*this);\r\n};<\/pre>\n<p>Note that this is private inheritance (can go without virtual destructors since STL doesn&#8217;t have it). I have not exposed all the private members and methods back to public with the &#8216;using&#8217; keyword yet, but you get the idea.<\/p>\n<hr \/>\n<p>This is how I normally want the extended container to be used:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\">int main()\r\n{\r\n    MapWithDefault&lt;string, int&gt; m(17);  \/\/ Endowed with default of 17\r\n    cout &lt;&lt; \"pull rabbit from m.read_only:  \" &lt;&lt; m.read_only[\"rabbit\"] &lt;&lt; endl;   \/\/ Should read 17\r\n\r\n    \/\/ Demonstrates commonly unwanted behavior of inserting requested key when not found\r\n    cout &lt;&lt; \"pull rabbit from m:            \" &lt;&lt; m[\"rabbit\"] &lt;&lt; endl; \/\/ Should read 0 because the key was inserted (not default anymore)\r\n\r\n    \/\/ Won't compile: demonstrate that it's read only\r\n    \/\/ m.read_only[\"rabbit\"] = 42;\r\n\r\n    \/\/ Demonstrate writing\r\n    m[\"rabbit\"] = 42;\r\n\r\n    \/\/ Confirms written value\r\n    cout &lt;&lt; \"pull rabbit from m_read_only:  \" &lt;&lt; m.read_only[\"rabbit\"] &lt;&lt; endl;   \/\/ Should read 42\r\n    cout &lt;&lt; \"pull rabbit from m:            \" &lt;&lt; m[\"rabbit\"] &lt;&lt; endl;             \/\/ Should read 42\r\n\r\n    return 0;\r\n}\r\n<\/pre>\n<p>Basically, for read-only operations, always operate directly on the chained &#8216;<span style=\"font-family: 'courier new', courier, monospace;\">m.read_only<\/span>&#8216; object reference: it will make sure the const protected version of the methods (including read-only <span style=\"font-family: 'courier new', courier, monospace;\">operator[]<\/span>) is called.<\/p>\n<hr \/>\n<p>Please let me know if it&#8217;s a bad idea or there&#8217;s some details I&#8217;ve missed!<\/p>\n<p>&nbsp;<\/p>\n<div class=\"pvc_clear\"><\/div>\n<p id=\"pvc_stats_655\" class=\"pvc_stats all  \" data-element-id=\"655\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"far\" data-icon=\"chart-bar\" role=\"img\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\" class=\"svg-inline--fa fa-chart-bar fa-w-16 fa-2x\"><path fill=\"currentColor\" d=\"M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z\" class=\"\"><\/path><\/svg><\/i> <img loading=\"lazy\" decoding=\"async\" width=\"16\" height=\"16\" alt=\"Loading\" src=\"https:\/\/wonghoi.humgar.com\/blog\/wp-content\/plugins\/page-views-count\/ajax-loader-2x.gif\" border=0 \/><\/p>\n<div class=\"pvc_clear\"><\/div>\n","protected":false},"excerpt":{"rendered":"<p>I recently watched Louis Brandy&#8217;s CppCon presentation \u201cCuriously Recurring C++ Bugs at Facebook\u201d on youtube. For\u00a0bug#2, which is a well-known trap for STL map-based containers, operator[] will insert the requested key (associated with a default-constructed value) if it is not &hellip; <a href=\"https:\/\/wonghoi.humgar.com\/blog\/2017\/11\/18\/c-annoyances-and-reliefs-operator-in-stl-map-based-containers\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n<div class=\"pvc_clear\"><\/div>\n<p id=\"pvc_stats_655\" class=\"pvc_stats all  \" data-element-id=\"655\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"far\" data-icon=\"chart-bar\" role=\"img\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\" class=\"svg-inline--fa fa-chart-bar fa-w-16 fa-2x\"><path fill=\"currentColor\" d=\"M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z\" class=\"\"><\/path><\/svg><\/i> <img loading=\"lazy\" decoding=\"async\" width=\"16\" height=\"16\" alt=\"Loading\" src=\"https:\/\/wonghoi.humgar.com\/blog\/wp-content\/plugins\/page-views-count\/ajax-loader-2x.gif\" border=0 \/><\/p>\n<div class=\"pvc_clear\"><\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[25,29],"tags":[],"class_list":["post-655","post","type-post","status-publish","format-standard","hentry","category-cpp","category-programming"],"_links":{"self":[{"href":"https:\/\/wonghoi.humgar.com\/blog\/wp-json\/wp\/v2\/posts\/655","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wonghoi.humgar.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wonghoi.humgar.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wonghoi.humgar.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/wonghoi.humgar.com\/blog\/wp-json\/wp\/v2\/comments?post=655"}],"version-history":[{"count":25,"href":"https:\/\/wonghoi.humgar.com\/blog\/wp-json\/wp\/v2\/posts\/655\/revisions"}],"predecessor-version":[{"id":688,"href":"https:\/\/wonghoi.humgar.com\/blog\/wp-json\/wp\/v2\/posts\/655\/revisions\/688"}],"wp:attachment":[{"href":"https:\/\/wonghoi.humgar.com\/blog\/wp-json\/wp\/v2\/media?parent=655"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wonghoi.humgar.com\/blog\/wp-json\/wp\/v2\/categories?post=655"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wonghoi.humgar.com\/blog\/wp-json\/wp\/v2\/tags?post=655"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}