Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template function to extract value out of several nested unordered_map's

Let's assume I have a nested std::unordered_map that looks like this:

std::unordered_map<ResourceName, std::unordered_map<HAL::ResourceFormat::Color, HAL::RTDescriptor>>

I want a function that will return a pointer to HAL::RTDescriptor based on two keys ResourceName and HAL::ResourceFormat::Color if object is present or nullptr otherwise. The straightforward implementation looks like this:

const HAL::RTDescriptor* ResourceDescriptorStorage::GetRTDescriptor(ResourceName resourceName, HAL::ResourceFormat::Color format) const
    {
        auto mapIt = mRTDescriptorMap.find(resourceName);

        if (mapIt == mRTDescriptorMap.end()) {
            return nullptr;
        }

        auto& nestedMap = mapIt->second;
        auto nestedMapIt = nestedMap.find(format);

        if (nestedMapIt == nestedMap.end()) {
            return nullptr;
        }

        return &nestedMapIt->second;
    }

Is there a way to use templates to generalize the logic? Something with parameter packs for keys. Something that will go through each nested container, check for object availability and return it or nullptr at the end:

template<
        template<class...> class AssociativeContainer,
        class... Keys
    >
        decltype(auto) Find(const AssociativeContainer<...>& rootContainer, Keys&&... keys)
    {
        ...
    }
like image 588
Pavlo Muratov Avatar asked Dec 23 '22 21:12

Pavlo Muratov


2 Answers

Simpler solution (requires C++17):

template<class AssociativeContainer, class Key, class... Keys>
auto Find(const AssociativeContainer& container, Key&& key, Keys&&... keys){
    auto it = container.find(std::forward<Key>(key));
    bool found = it != container.end();
    if constexpr(sizeof...(Keys) == 0)
        return found ? &it->second : nullptr;
    else
        return found ? Find(it->second, std::forward<Keys>(keys)...) : nullptr;
}

This also allows to get a reference to any inbetween container, as it doesn't require to pass all keys.

like image 116
krisz Avatar answered Dec 28 '22 13:12

krisz


Is there a way to use templates to generalize the logic? Something with parameter packs for keys. Something that will go through each nested container, check for object availability and return it or nullptr at the end:

Require a little of work (maybe someone more expert than me can make it simpler) but sure it's possible.

By example... given a custom type traits (and using to simplify the use) as follows

template <typename T>
struct lastType
 { using type = T; };

template <template <typename...> class C, typename K, typename V>
struct lastType<C<K, V>> : public lastType<V>
 { };

template <typename T>
using lastType_t = typename lastType<T>::type;

you can write Find() recursively as follows

// ground case
template <typename V>
V const * Find (V const & val)
 { return &val; }

// recursion case
template <typename C, typename K0, typename ... Ks>
lastType_t<C> const * Find (C const & cnt, K0 && key0, Ks && ... keys)
 {
   auto mapIt = cnt.find(std::forward<K0>(key0));

   if ( mapIt == cnt.cend() ) 
      return nullptr;

   return Find(mapIt->second, std::forward<Ks>(keys)...);
 }

The following is a full compiling example

#include <map>
#include <string>
#include <iostream>
#include <unordered_map>

template <typename T>
struct lastType
 { using type = T; };

template <template <typename...> class C, typename K, typename V>
struct lastType<C<K, V>> : public lastType<V>
 { };

template <typename T>
using lastType_t = typename lastType<T>::type;

template <typename V>
V const * Find (V const & val)
 { return &val; }

template <typename C, typename K0, typename ... Ks>
lastType_t<C> const * Find (C const & cnt, K0 && key0, Ks && ... keys)
 {
   auto mapIt = cnt.find(std::forward<K0>(key0));

   if ( mapIt == cnt.cend() ) 
      return nullptr;

   return Find(mapIt->second, std::forward<Ks>(keys)...);
 }

using typeC = std::map<int,
                 std::unordered_map<std::string,
                    std::unordered_map<long,
                       std::map<char, long long>>>>;

int main ()
 {
   typeC c;

   c[0]["one"][2l]['3'] = 4ll;

   auto v = Find(c, 0, "one", 2l, '3');

   std::cout << (*v) << std::endl;

   static_assert( std::is_same_v<decltype(v), long long const *>, "!" );
 }

-- EDIT --

I'm particularly dumb today: as highlighted by krisz in his answer (thanks), the ternary operator permit the use of auto as return type (from C++14).

So there is no needs of the lastType custom type traits and Find() can be simply written as

// ground case
template <typename V>
V const * Find (V const & val)
 { return &val; }

// recursion case
template <typename C, typename K0, typename ... Ks>
auto Find (C const & cnt, K0 && key0, Ks && ... keys)
 {
   auto mapIt = cnt.find(std::forward<K0>(key0));

   return mapIt == cnt.cend()
      ? nullptr
      : Find(mapIt->second, std::forward<Ks>(keys)...);
 }

For C++11, the recursion case require also the trailing return type; by example

-> decltype(Find(cnt.find(std::forward<K0>(key0))->second, std::forward<Ks>(keys)...))
like image 30
max66 Avatar answered Dec 28 '22 15:12

max66