{"id":108462,"date":"2023-07-19T07:00:00","date_gmt":"2023-07-19T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108462"},"modified":"2023-07-19T06:49:57","modified_gmt":"2023-07-19T13:49:57","slug":"20230719-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230719-00\/?p=108462","title":{"rendered":"How to clone a Windows Runtime map in the face of possible concurrent modification, part 1"},"content":{"rendered":"<p>So far, we&#8217;ve been looking at cloning a Windows Runtime vector in the face of possible concurrent modification. We can apply what we&#8217;ve learned to Windows Runtime maps.<\/p>\n<p>The idea is basically the same: Use <code>GetMany<\/code> to capture the contents atomically. The wrinkle is that there is no <code>GetMany<\/code> method on a Windows Runtime map. We&#8217;ll have to get the iterator and call the iterator&#8217;s <code>GetMany<\/code> method. But going to the iterator means that we could get a <code>hresult_<wbr \/>changed_<wbr \/>state<\/code> exception if the map mutates between the time we obtain the iterator and the time we try to read from it.\u00b9<\/p>\n<pre>template&lt;typename M&gt;\r\nauto clone_as_map(M const&amp; m)\r\n-&gt; std::map&lt;\r\n        decltype(m.First().Current().Key()),\r\n        decltype(m.First().Current().Value())&gt;\r\n{\r\n    using KVP = decltype(m.First().Current());\r\n    std::vector&lt;KVP&gt; pairs;\r\n    using K = decltype(KVP().Key());\r\n    using V = decltype(KVP().Value());\r\n    uint32_t expected;\r\n    uint32_t actual;\r\n    do {\r\n        expected = m.Size();\r\n        pairs.resize(expected + 1);\r\n        try {\r\n            actual = m.First().GetMany(pairs);\r\n        } catch (winrt::hresult_changed_state const&amp;) {\r\n            continue;\r\n        }\r\n    } while (actual &gt; expected);\r\n    pairs.erase(pairs.begin() + actual, pairs.end());\r\n    std::map&lt;K, V&gt; map;\r\n    for (auto&amp;&amp; pair : pairs) {\r\n        map.emplace(pair.Key(), pair.Value());\r\n    }\r\n    return map;\r\n}\r\n\r\ntemplate&lt;typename M&gt;\r\nauto CloneMap(M const&amp; m)\r\n-&gt; winrt::Windows::Foundation::\r\n        Collections::IMap&lt;\r\n            decltype(m.First().Current().Key()),\r\n            decltype(m.First().Current().Value())&gt;\r\n{\r\n    return winrt::multi_threaded_map(\r\n        clone_as_map(m));\r\n}\r\n<\/pre>\n<p>Note that we don&#8217;t need to use <code>winrt_empty_value&lt;T&gt;()<\/code> when resizing the <code>pairs<\/code> vector because the value type of the vector is <code>IKeyValuePair&lt;K, V&gt;<\/code>, which is an interface type and therefore has a default constructor that creates an empty smart pointer.<\/p>\n<p>Note also that I provide an explicit trailing return type instead of allowing the compiler to infer it from the <code>return<\/code> statement. This allows the return type to participate in SFINAE. Unfortunately, representing this trailing return type is rather cumbersome because we have to write it out without the assistance of the <code>K<\/code> and <code>V<\/code> types defined in the function body.<\/p>\n<p>The C++\/WinRT library allows you to create <code>IMap<\/code> implementations that are based either on <code>std::map<\/code> or <code>std::unordered_map<\/code>. The version above always uses <code>std::map<\/code> with the same key and value as the original. But maybe we want to let you choose a <code>std::unordered_map<\/code> or tweak the key and value types.<\/p>\n<p>We can use <!-- backref: Reordering C++ template type parameters for usability purposes, and type deduction from the future --> type deduction from the future to default to using a <code>std::map<\/code> that matches the inbound parameter, but let you pick a different collection if you like.<\/p>\n<pre><span style=\"border: solid 1px currentcolor; border-bottom: none;\">template&lt;typename, typename = void&gt;            <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">struct has_reserve_member : std::false_type {};<\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">template&lt;typename T&gt;                           <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">struct has_reserve_member&lt;T,                   <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">    std::void_t&lt;decltype(                      <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">        std::declval&lt;T&gt;().reserve(0))&gt;&gt;        <\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">    : std::true_type {};                       <\/span>\r\n\r\ntemplate&lt;typename R = void, typename M&gt;\r\nauto clone_as_map(M const&amp; m)\r\n-&gt; <span style=\"border: solid 1px currentcolor; border-bottom: none;\">using C = std::conditional_t&lt;<\/span>\r\n   <span style=\"border: 1px currentcolor; border-style: none solid;\"> std::is_same_v&lt;R, void&gt;,    <\/span>\r\n   <span style=\"border: 1px currentcolor; border-style: none solid;\"> std::map&lt;K, V&gt;,             <\/span>\r\n   <span style=\"border: solid 1px currentcolor; border-top: none;\"> R&gt;                          <\/span>\r\n{\r\n    using KVP = decltype(m.First().Current());\r\n    std::vector&lt;KVP&gt; pairs;\r\n    using K = decltype(KVP().Key());\r\n    using V = decltype(KVP().Value());\r\n    uint32_t expected;\r\n    uint32_t actual;\r\n    do {\r\n        expected = m.Size();\r\n        pairs.resize(expected + 1);\r\n        try {\r\n            actual = m.First().GetMany(pairs);\r\n        } catch (hresult_changed_state const&amp;) {\r\n            continue;\r\n        }\r\n    } while (actual &gt; expected);\r\n    pairs.resize(actual);\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">using C = std::conditional_t&lt;                <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::is_same_v&lt;R, void&gt;,                 <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::map&lt;K, V&gt;,                          <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    R&gt;;                                      <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">C map;                                       <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">if constexpr (has_reserve_member&lt;C&gt;::value) {<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    map.reserve(actual);                     <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                            <\/span>\r\n    for (auto&amp;&amp; pair : pairs) {\r\n        map.emplace(pair.Key(), pair.Value());\r\n    }\r\n    return map;\r\n}\r\n\r\ntemplate&lt;typename R = void, typename M&gt;\r\nauto CloneAsMap(M const&amp; m)\r\n-&gt; winrt::Windows::Foundation::\r\n        Collections::IMap&lt;\r\n            decltype(m.First().Current().Key()),\r\n            decltype(m.First().Current().Value())&gt;\r\n{\r\n    return winrt::multi_threaded_map(\r\n        clone_as_map&lt;R&gt;(m));\r\n}\r\n<\/pre>\n<p>We create a detector class to see whether the underlying type has a <code>reserve<\/code> method (which <code>unordered_map<\/code> has); if so, then we call it to allow the container to prepare for the incoming elements.<\/p>\n<p>An example usage of this new feature would be<\/p>\n<pre>winrt::Windows::Foundation::Collections::\r\n    IMap&lt;winrt::hstring, int32_t&gt; v = \/* ... *\/;\r\nauto clone = CloneAsMap&lt;\r\n    std::unordered_map&lt;winrt::hstring, int64_t,\r\n        case_insensitive_hash, case_insensitive_equal&gt;&gt;\r\n    (v);\r\n<\/pre>\n<p>This takes the original <code>IMap<\/code> and clones it into a new <code>IMap<\/code> which uses <code>int64_t<\/code> as its value type rather than <code>int32_t<\/code> (said conversion occuring through integer promotion), and which uses a custom hash class and custom equality class which treat the string as a case-insensitive string. We changed the underlying container to an <code>unordered_map<\/code>, changed the value type, and chose non-default hash and equality classes.<\/p>\n<p>Another thing we might do to this function is add a lambda that lets you convert the original key-value pair to an arbitrary <code>std::pair<\/code>, but arguably that&#8217;s really a job for <code>std::transform<\/code>.<\/p>\n<p>But maybe we&#8217;re working too hard. We&#8217;ll try another approach next time.<\/p>\n<p>\u00b9 Note, however, that the iterator is not <i>required<\/i> to throw a state changed exception. So we cannot rely on the exception to tell us that a concurrent mutation has occurred.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Applying what we learned about vectors.<\/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-108462","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Applying what we learned about vectors.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108462","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=108462"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108462\/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=108462"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108462"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108462"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}