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

前端 未结 3 630
孤独总比滥情好
孤独总比滥情好 2021-01-01 18:43
template struct Printer;

// I want this to match std::vector (and similar linear containers) 
template class T, clas         


        
相关标签:
3条回答
  • 2021-01-01 19:24

    Your question is a bit ambiguous as there are also containers which are neither sequential, nor "key-value", e.g. set. I take it you meant to distinguish sequence from associative containers?

    If that is the case you can rely on the fact that associative containers have key_type, while the sequence containers do not. Here's a 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();
    }
    

    Live example

    0 讨论(0)
  • 2021-01-01 19:35

    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.

    0 讨论(0)
  • 2021-01-01 19:44

    The problem here is that

    template <class, class...> T
    

    and

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

    both match any template classes that have at least 2 template parameters which is the case in both your examples. One thing you can do is to make both template parameter lists more specific, like 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 it working for std::vector and std::map here: http://coliru.stacked-crooked.com/a/7f6b8546b1ab5ba9

    Another possibility is to use SFINAE (actually I'd 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 you wanted to match something 'std::vector'-like, not specifically std::vector. The first method however should at least differentiate between std::vector and std::map. If you want to write algorithms for containers with different ways to iterate over, why not write your functions for iterators and differentiate between those?

    Edit2: The code before was miserably wrong. However it works now.

    0 讨论(0)
提交回复
热议问题