Disambiguate template specialization between map-like and vector-like containers

c++, c++11, stl, template-specialization, templates

Solution

The problem with the pattern-matching approach is that it will only ever work if for every single container you write a specialization. This is tedious work.

Instead you can rely on other properties:

- a container will necessarily be iterable over via `begin(c)` and `end(c)` expressions

- on top of this, an associative container will have a `::key_type` nested type, among others, as expressed in § 23.2.4 [associative.rqmts].

Therefore, we can whip up a classifier, based on tag dispatching:

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 might not be exactly what you wish for, because under this requirements a `set` is an associative container, but its value is not a `pair`, so you cannot print `key: value`. You have to adapt the tag-dispatching to your needs.

Problem

``` 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` both match the second specialization. I think it's because `std::vector`'s allocator parameter gets matched to `TV`, which is intended for `std::map`'s value. How can I match `std::vector` (and other linear containers) with the first specialization and `std::map` (and other key-value containers) with the second one?

Original source