Disambiguate специализация шаблонов между картографическими и векторными контейнерами
template<class> struct Printer;
// I want this to match std::vector (and similar linear containers)
template<template<class, class...> class T, class TV, class... TS>
struct Printer<T<TV, TS...>> { ... };
// I want this to match std::map (and similar map-like containers)
template<template<class, class, class...> class TM, class TK, class TV, typename... TS>
struct Printer<TM<TK, TV, TS...>> { ... }
int main()
{
// Both of these match the second specialization, which is only intended
// for std::map (and similar map-like containers)
Printer<std::vector<int>>::something();
Printer<std::map<int, float>>::something();
}
Как видно из примера, std::vector
и std::map
соответствуют второй специализации. Я думаю, это потому, что параметр std::vector
allocator сопоставляется с TV
, который предназначен для значения std::map
.
Как я могу сопоставить std::vector
(и другие линейные контейнеры) с первой специализацией и std::map
(и другими контейнерами с ключом) со вторым?
Ответы
Ответ 1
Проблема с подходом к сопоставлению шаблонов заключается в том, что он будет работать только тогда, если для каждого отдельного контейнера вы напишете специализацию. Это утомительная работа.
Вместо этого вы можете полагаться на другие свойства:
- контейнер обязательно будет итерироваться через выражения
begin(c)
и end(c)
- вместо этого ассоциативный контейнер будет иметь
::key_type
вложенный тип, среди прочих, как выражено в § 23.2.4 [associative.rqmts].
Поэтому мы можем взломать классификатор на основе отправки тегов:
inline constexpr auto is_container_impl(...) -> std::false_type {
return std::false_type{};
}
template <typename C>
constexpr auto is_container_impl(C const* c) ->
decltype(begin(*c), end(*c), std::true_type{})
{
return std::true_type{};
}
template <typename C>
constexpr auto is_container(C const& c) -> decltype(is_container_impl(&c)) {
return is_container_impl(&c);
}
inline constexpr auto is_associative_container_impl(...)
-> std::false_type
{ return std::false_type{}; }
template <typename C, typename = typename C::key_type>
constexpr auto is_associative_container_impl(C const*) -> std::true_type {
return std::true_type{};
}
template <typename C>
constexpr auto is_associative_container(C const& c)
-> decltype(is_associative_container_impl(&c))
{
return is_associative_container_impl(&c);
}
И теперь вы можете написать "простой" код:
template <typename C>
void print_container(C const& c, std::false_type/*is_associative*/) {
}
template <typename C>
void print_container(C const& c, std::true_type/*is_associative*/) {
}
template <typename C>
void print_container(C const& c) {
return print_container(C, is_assocative_container(c));
}
Теперь это может быть не совсем то, что вы хотите, потому что в соответствии с этими требованиями set
является ассоциативным контейнером, но его значение не является pair
, поэтому вы не можете печатать key: value
. Вам необходимо адаптировать отправку тегов к вашим потребностям.
Ответ 2
Ваш вопрос немного неоднозначен, поскольку существуют также контейнеры, которые не являются ни последовательными, ни "ключевыми" значениями, например. set
. Полагаю, вы хотели отличить последовательность от ассоциативных контейнеров?
Если это так, вы можете положиться на то, что ассоциативные контейнеры имеют key_type
, а контейнеры последовательности - нет. Здесь решение:
#include <type_traits>
#include <vector>
#include <map>
template<class, class = void>
struct IsAssociativeContainer
: std::false_type {};
template<class T>
struct IsAssociativeContainer<T,
typename std::enable_if<sizeof(typename T::key_type)!=0>::type>
: std::true_type {};
template<class T, bool = IsAssociativeContainer<T>::value>
struct Printer;
// I want this to match std::vector (and similar linear containers)
template<template<class, class...> class T, class TV, class... TS>
struct Printer<T<TV, TS...>, false> { static void something(); };
// I want this to match std::map (and similar map-like containers)
template<template<class, class, class...> class TM, class TK, class TV, typename... TS>
struct Printer<TM<TK, TV, TS...>, true> { static void something(); };
int main()
{
// Both of these match the second specialization, which is only intended
// for std::map (and similar map-like containers)
Printer<std::vector<int>>::something();
Printer<std::map<int, float>>::something();
}
Живой пример
Ответ 3
Проблема здесь в том, что
template <class, class...> T
и
template <class, class, class...> TM
оба соответствуют любым классам шаблонов, которые имеют как минимум два параметра шаблона, что и в обоих примерах. Одна вещь, которую вы можете сделать, - это сделать списки параметров шаблонов более конкретными, например, например:
template <class>
struct Printer;
template <template<typename, typename> class C, template <typename> class A, typename T>
struct Printer< C<T, A<T>> > {
...
};
template <template<typename, typename, typename, typename> class C, template <typename> class Comp, template <typename> class A, typename K, typename T>
struct Printer< C<K, T, Comp<K>, A<std::pair<const K,T>>> > {
...
};
Вы можете видеть, что он работает для std::vector и std:: map здесь: http://coliru.stacked-crooked.com/a/7f6b8546b1ab5ba9
Другая возможность - использовать SFINAE (на самом деле я бы рекомендовал использовать его в обоих сценариях):
template<template<class, class...> class T, class TV, class... TS, class = typename std::enable_if<std::is_same<T, std::vector>::value>::type>
struct Printer<T<TV, TS...>> { ... };
template<template<class, class, class...> class TM, class TK, class TV, typename... TS, class = typename std::enable_if<std::is_same<T, std::map>::value>::type>
struct Printer<TM<TK, TV, TS...>> { ... }
Изменить: Oups, просто прочитайте в комментариях, которые вы хотите сопоставить, что-то "std::vector" - вроде, а не конкретно std::vector. Однако первый метод должен по крайней мере различать std::vector и std:: map. Если вы хотите написать алгоритмы для контейнеров с разными способами перебора, почему бы не написать свои функции для итераторов и не дифференцировать их?
Edit2: Код раньше был ужасно неправильным. Однако он работает сейчас.