Подтвердить что ты не робот

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 (и другими контейнерами с ключом) со вторым?

4b9b3361

Ответ 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: Код раньше был ужасно неправильным. Однако он работает сейчас.