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?

Daniel
  • 8,179
  • 6
  • 31
  • 56

3 Answers3

22

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
}
T.C.
  • 133,968
  • 17
  • 288
  • 421
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • 1
    `is_mappish_impl` made me chuckle. :) – erip Feb 09 '16 at 14:05
  • although, this solution is also only valid when the non standard map implements the interface of standard associative container, this is also may break. – David Haim Feb 09 '16 at 14:19
  • 3
    @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. – Jonathan Wakely Feb 09 '16 at 15:04
  • 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`? – sancho.s ReinstateMonicaCellio Feb 08 '20 at 10:46
  • @sancho.sReinstateMonica yes of course, just write a function template that returns the value of the `is_mappish` trait for the function parameter type. – Jonathan Wakely Feb 09 '20 at 13:05
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
}
matsjoyce
  • 5,744
  • 6
  • 31
  • 38
David Haim
  • 25,446
  • 3
  • 44
  • 78
  • 3
    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. – Daniel Feb 09 '16 at 13:53
  • @Daniel then add a specialization for unordered_map if thats needed – Serve Laurijssen Feb 09 '16 at 13:56
  • @Daniel 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.. – David Haim Feb 09 '16 at 13:56
  • I think this will break for maps with custom allocators and comparators. It can easily be fixed by adding two more template parameters. – Jordan Melo Feb 09 '16 at 20:34
  • 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`? – sancho.s ReinstateMonicaCellio Feb 08 '20 at 10:46
  • This also doesn't work for `const std::map`, `std::map&`, etc. You can use `std::decay` before feeding it into `isMap`. – MicroVirus Feb 11 '21 at 11:41
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>>;
Daniel
  • 8,179
  • 6
  • 31
  • 56
  • 1
    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). – Jonathan Wakely Feb 09 '16 at 14:02
  • 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`? – sancho.s ReinstateMonicaCellio Feb 08 '20 at 10:46