Disambiguate specialization of templates between map and vector containers

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

As you can see from the example, std::vector and std::map correspond to the second specialization. I think this is because the std::vector allocator parameter maps to TV , which is for the std::map value.

How can I map std::vector (and other linear containers) to the first specialization and std::map (and other containers with the key) to the second?

+13
c ++ c ++ 11 stl templates template-specialization
Feb 09 '14 at 14:32
source share
3 answers

The problem with the pattern matching approach is that it will only work if you write a specialization for each individual container. This is a tedious job.

Instead, you can rely on other properties:

  • the container will be iterated through the expressions begin(c) and end(c)
  • instead, the associative container will have a ::key_type nested type, among others, as expressed in ยง 23.2.4 [associative.rqmts] .

Therefore, we can crack the classifier based on sending tags :

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

And now you can write "simple" code:

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

Now this may not be exactly what you want, because according to these requirements set is an associative container, but its value is not pair , so you cannot print key: value . You need to tailor tagging to your needs.

+15
Feb 09 '14 at 16:08
source share

Your question is a bit ambiguous, as there are also containers that are neither sequential nor โ€œkeyโ€ values, for example. set . I suppose you wanted to distinguish sequence from associative containers?

If so, you can rely on associative containers to have key_type , and sequence containers to not. Here's the solution:

 #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(); } 

Living example

+3
Feb 09 '14 at 16:02
source share

The problem here is that

 template <class, class...> T 

and

 template <class, class, class...> TM 

both correspond to any template classes that have at least two template parameters, as in both examples. One thing you can do is make the template parameter lists more specific, for example, for example:

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

You can see that it works for std :: vector and std :: map here: http://coliru.stacked-crooked.com/a/7f6b8546b1ab5ba9

Another possibility is to use SFINAE (in fact, I would recommend using it in both scenarios):

 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...>> { ... } 

Edit: Oups, just read in the comments that you want to map something to "std :: vector" - sort of, and not specifically std :: vector. However, the first method should at least distinguish between std :: vector and std :: map. If you want to write algorithms for containers with different search methods, why not write your own functions for iterators and differentiate them?

Edit2: The code used to be terribly wrong. However, it works now.

+1
Feb 09 '14 at
source share



All Articles