Although we’ve figured how to clone a Windows Runtime map in the face of possible concurrent modification, we’re still fiddling around with the best design for the function that does the cloning.
Last time, we wrote a one-shot function that does everything, but maybe the answer is to not try to jam everything into one function.
We can break things into two functions, one for using a std::
and one for using a std::
. (This also avoids problems if the user tries to call the original function with something like clone_
.)
template<typename M> auto clone_as_kvp_vector(M const& m) { using KVP = decltype(m.First().Current()); std::vector<KVP> pairs; uint32_t expected; uint32_t actual; do { expected = m.Size(); pairs.resize(expected + 1); try { actual = m.First().GetMany(pairs); } catch (winrt::hresult_changed_state const&) { continue; } } while (actual > expected); pairs.resize(actual); return pairs; }
This worker function does the real work of cloning the Windows Runtime map into a C++ vector of IKeyÂValueÂPair
objects.
The next step is to write some helpers to cut down on the repetitive typing that happens when you do type deduction from the future:
template<typename Override, typename Fallback> using override_or_fallback_t = std::conditional_t< std::is_same_v<Override, void>, Fallback, Override>; template<typename M, typename KeyOverride, typename ValueOverride> struct inferred_runtime_map_traits { using KVP = decltype(std::declval<M>().First().Current()); using Key = override_or_fallback_t< KeyOverride, decltype(KVP().Key())>; using Value = override_or_fallback_t< ValueOverride, decltype(KVP().Value())>; }; template<typename M, typename KeyOverride, typename ValueOverride, typename CompareOverride> struct inferred_map_traits { using base = inferred_runtime_map_traits <M, KeyOverride, ValueOverride>; using Key = typename base::Key; using Value = typename base::Value; using Compare = override_or_fallback_t< CompareOverride, std::less<Key>>; using type = std::map <Key, Value, Compare>; }; template<typename M, typename KeyOverride, typename ValueOverride, typename HashOverride, typename KeyEqualOverride> struct inferred_unordered_map_traits { using base = inferred_runtime_map_traits <M, KeyOverride, ValueOverride>; using Key = typename base::Key; using Value = typename base::Value; using Hash = override_or_fallback_t< HashOverride, std::hash<Key>>; using KeyEqual = override_or_fallback_t< KeyEqualOverride, std::equal_to<Key>>; using type = std::unordered_map <Key, Value, Hash, KeyEqual>; };
We start with override_
, a type alias that encapsulates the “use the type provided if it is not void
; otherwise use a fallback type.” We use this to build up an inferred_
which plucks out the KVP
, Key
, and Value
from a Windows Runtime map. We also build an inferred_
inferred_
which does the same for the additional template type parameters of a std::
and std::
.
We can use these traits types to save ourselves typing in the next few functions.
template<typename Key = void, typename Value = void, typename Compare = void, typename M, typename Traits = inferred_map_traits <M, Key, Value, Compare>> auto clone_as_map(M const& m, typename Traits::Compare const& compare = {}) { auto pairs = clone_as_kvp_vector(m); typename Traits::type map(compare); for (auto&& pair : pairs) { map.emplace_hint( map.end(), pair.Key(), pair.Value()); } return map; } template<typename Key = void, typename Value = void, typename Hash = void, typename KeyEqual = void, typename M, typename Traits = inferred_unordered_map_traits <M, Key, Value, Hash, KeyEqual>> auto clone_as_unordered_map(M const& m, typename Traits::Hash const& hash = {}, typename Traits::KeyEqual const& equal = {}) { auto pairs = clone_as_kvp_vector(m); typename Traits::type map( static_cast<uint32_t>(pairs.size()), hash, equal); for (auto&& pair : pairs) { map.emplace(pair.Key(), pair.Value()); } return map; } template<typename Key = void, typename Value = void, typename Compare = void, typename M, typename Traits = inferred_map_traits <M, Key, Value, Compare>> auto CloneMap(M const& m, typename Traits::Compare const& compare = {}) { return winrt::multi_threaded_map( clone_as_map<Key, Value, Compare> (m, compare)); } template<typename Key = void, typename Value = void, typename Hash = void, typename KeyEqual = void, typename M, typename Traits = inferred_unordered_map_traits <M, Key, Value, Hash, KeyEqual>> auto CloneUnorderedMap(M const& m, typename Traits::Hash const& hash = {}, typename Traits::KeyEqual const& equal = {}) { return winrt::multi_threaded_map( clone_as_unordered_map<Key, Value, Hash, KeyEqual> (m, hash, equal)); }
We front-load the template type parameters that belong to the map or unordered map, so that you can write something that looks almost natural.
// Create a std::unordered_map from strings to strings // but where the string keys are treated as case-insensitive. auto clone = clone_unordered_map< winrt::hstring, winrt::hstring, case_insensitive_string_hash, case_insensitive_string_equal>(original);
Most of this wackiness was just C++ template metaprogramming nonsense.
Notice that we used std::
to optimize for the case that the keys are returned in sorted order.
Next time, we’ll port this to C++/CX.
0 comments