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)
{
...
}
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.
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)...))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With