Проверка типа карты

Иногда мне приходится писать общие процедуры, которые могут быть применены к контейнеру объектов, или карта таких контейнеров (т.е. обрабатывать каждый контейнер на карте). Один из подходов состоит в том, чтобы написать отдельные подпрограммы для типов карт, но я думаю, что более естественным и менее подробным будет одна подпрограмма, которая работает для обоих типов ввода:

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

Что такое безопасный, чистый способ отправки этой теги?

Ответ 1

Существующие ответы на ответы для очень специфических свойств std::map, либо то, что это точно специализация std::map (что было бы неверно для std::unordered_map или нестандартных типов с тем же интерфейсом, что и std::map), или тестирование, что его value_type точно std::pair<const key_type, mapped_type> (что было бы верно для multimap и unordered_map, но false для нестандартных типов с похожими интерфейсами).

Это только проверяет, что он предоставляет члены key_type и mapped_type, и к ним можно получить доступ с помощью operator[], поэтому не говорит, что std::multimap является 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 { };

Поскольку is_mappish имеет "базовую характеристику" либо true_type, либо false_type, вы можете отправить на него так:

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

Или вы можете полностью отказаться от отправки и просто перегрузить foo для карт и не-карт:

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
}

Ответ 2

Это сработало для меня, но не проверено на 100%:

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
}

Ответ 3

Вот что я придумал:

#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>>;