Search code examples
c++templatesrecursionvariadic-templates

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)
    {
        ...
    }

Solution

  • 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.