问题
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?
回答1:
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
}
回答2:
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
}
回答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>>;
来源:https://stackoverflow.com/questions/35293470/checking-if-a-type-is-a-map