Checking if a type is a map
Asked Answered
F

3

16

I sometimes find the need to write general routines that can be applied to a container of objects, or a map of such containers (i.e. process each container in the map). One approach is to write separate routines for map types, but I think it can be more natural and less verbose to have one routine that works for both types of input:

template <typename T>
auto foo(const T& items)
{ 
    return foo(items, /* tag dispatch to map or non-map */);
}

What is a safe, clean way to do this tag dispatch?

Freetown answered 9/2, 2016 at 13:35 Comment(1)
Check if it has nested key_type and mapped_type types?Carnegie
C
23

The existing answers test for very specific properties of std::map, either that it is precisely a specialization of std::map (which would be false for std::unordered_map or non-standard types with the same interface as std::map), or testing that its value_type is exactly std::pair<const key_type, mapped_type> (which would be true for multimap and unordered_map, but false for non-standard types with similar interfaces).

This only tests that it provides key_type and mapped_type members, and can be accessed with operator[], so doesn't say that std::multimap is mappish:

#include <type_traits>

namespace detail {
  // Needed for some older versions of GCC
  template<typename...>
    struct voider { using type = void; };

  // std::void_t will be part of C++17, but until then define it ourselves:
  template<typename... T>
    using void_t = typename voider<T...>::type;

  template<typename T, typename U = void>
    struct is_mappish_impl : std::false_type { };

  template<typename T>
    struct is_mappish_impl<T, void_t<typename T::key_type,
                                     typename T::mapped_type,
                                     decltype(std::declval<T&>()[std::declval<const typename T::key_type&>()])>>
    : std::true_type { };
}

template<typename T>
struct is_mappish : detail::is_mappish_impl<T>::type { };

Because is_mappish has a "base characteristic" of either true_type or false_type you can dispatch on it like so:

template <typename T>
auto foo(const T& items, true_type)
{
    // here be maps
}

template <typename T>
auto foo(const T& items, false_type)
{
    // map-free zone
}

template <typename T>
auto foo(const T& items)
{ 
    return foo(items, is_mappish<T>{});
}

Or you can avoid dispatching entirely, and just overload foo for maps and non-maps:

template <typename T,
          std::enable_if_t<is_mappish<T>{}, int> = 0>
auto foo(const T& items)
{
    // here be maps
}

template <typename T,
          std::enable_if_t<!is_mappish<T>{}, int> = 0>
auto foo(const T& items)
{
    // map-free zone
}
Carnegie answered 9/2, 2016 at 13:59 Comment(5)
is_mappish_impl made me chuckle. :)Ombudsman
although, this solution is also only valid when the non standard map implements the interface of standard associative container, this is also may break.Waggoner
@DavidHaim, yes obviously. If the type doesn't implement a specific interface then you probably don't want to dispatch to a template function that relies on that interface. The trait can easily be tailored to check whatever parts of the map interface are relevant to the OP's foo that works with maps, for whatever definition of "map" is relevant to the function.Carnegie
Can this be used on a variable var, in such a way that (generically naming is_map to a function) is_map(var) returns a bool?Descendent
@sancho.sReinstateMonica yes of course, just write a function template that returns the value of the is_mappish trait for the function parameter type.Carnegie
W
12

This has worked for me, not tested 100% though:

template <class T>
struct isMap {
    static constexpr bool value = false;
};

template<class Key,class Value>
struct isMap<std::map<Key,Value>> {
    static constexpr bool value = true;
};

int main() {
    constexpr bool b1 = isMap<int>::value; //false
    constexpr bool b2 = isMap<std::vector<int>>::value; //false
    constexpr bool b3 = isMap<std::map<int,std::string>>::value; //true
    constexpr bool b4 = isMap<std::future<int>>::value; //false
}
Waggoner answered 9/2, 2016 at 13:45 Comment(6)
Worth noting this only works for std::map and not the other 'map' types (e.g. std::unordered_map). I should have probably been more specific in the question.Freetown
@Freetown then add a specialization for unordered_map if thats neededAlert
@Freetown I was assuming you talked about standard regular map. this can be easily be converted to support std::unordered_map , and subsequently isAnyMap. even though , I doubt that someone will use more then 1-2 types of maps in the same project..Waggoner
I think this will break for maps with custom allocators and comparators. It can easily be fixed by adding two more template parameters.Salangia
Can this be used on a variable var, in such a way that (generically naming is_map to a function) is_map(var) returns a bool?Descendent
This also doesn't work for const std::map, std::map&, etc. You can use std::decay before feeding it into isMap.Tarentarentum
F
3

Here's what I came up with:

#include <type_traits>
#include <utility>

namespace detail
{
    template <typename T, typename = void>
    struct IsMap : std::false_type {};

    template <typename T>
    struct IsMap<T, std::enable_if_t<
                        std::is_same<typename T::value_type,
                                    std::pair<const typename T::key_type,
                                              typename T::mapped_type>
                        >::value>
    > : std::true_type {};
}

template <typename T>
constexpr bool is_map = detail::IsMap<T>::value;

namespace { template <bool> struct MapTagImpl {}; }
using MapTag    = MapTagImpl<true>;
using NonMapTag = MapTagImpl<false>;

template <typename T>
using MapTagType = MapTagImpl<is_map<T>>;
Freetown answered 9/2, 2016 at 13:40 Comment(2)
Deriving from true_type or false_type is simpler than defining static constexpr bool value. Also your trait is true for multimaps, which might not be desirable (they don't have the same interface).Carnegie
Can this be used on a variable var, in such a way that (generically naming is_map to a function) is_map(var) returns a bool?Descendent

© 2022 - 2024 — McMap. All rights reserved.