Search code examples
c++c++14c++17template-argument-deductiontemplate-templates

Explicit match of template template arguments


Consider this function:

template<template<class, class> class C, class T, class Alloc>
void foo(C<T, Alloc>& container) {
    std::cout << container.size() << std::endl;
}

Function foo() accepts a std::vector<T, Alloc>, but it also accepts std::map<Key, T, Compare, Allocator> in C++17 because Compare and Allocator have default values. Note std::map fails in C++14.

How can I make foo() only accept templates with exactly only 2 template parameters, so it fails with std::map in C++17?


Solution

  • How can I make foo only accept templates with exactly only 2 template parameters, so it fails with std::map in C++17?

    If you want avoid that foo() accept a container accepting three or more template parameters (so fail with std::map) is relatively simple: you can follow the rtpax's suggestion or, given a custom type traits that say if a type is based on a two type accepting container

    template <typename>
    struct accept2 : std::false_type
     { };
    
    template <template <typename...> class C, typename X, typename Y>
    struct accept2<C<X, Y>> : std::true_type
     { };
    

    and a similar type traits for a three-accepting container

    template <typename>
    struct accept3 : std::false_type
     { };
    
    template <template <typename...> class C, typename X, typename Y, typename Z>
    struct accept3<C<X, Y, Z>> : std::true_type
     { };
    

    you can SFINAE enable foo() only if the deduced type accept two but doesn't accept three types

    template <typename C>
    std::enable_if_t<accept2<C>{} && !accept3<C>{}> foo (C const & container)
     { }
    

    But this solution (and also the rtpax one) has a problem: what about a container that receive before two template types and after one (or more) template not-type parameter with default values?

    Or two template types and one (or more) template-template parameter with default values? Maybe with different signatures?

    You can add specializations for accept3 to recognize the other cases but there are infinite combinations of type, non-type and template-template parameter, with default value, after the first two template type. So you have to write infinite specializations to intercept all cases.

    This is a little unpractical.

    And I suspect there isn't (in C++17) a practical solution.

    Anyway, a full compiling example (avoiding three or more template types containers) follows

    #include <map>
    #include <vector>
    #include <type_traits>
    
    template <typename>
    struct accept2 : std::false_type
     { };
    
    template <template <typename...> class C, typename X, typename Y>
    struct accept2<C<X, Y>> : std::true_type
     { };
    
    template <typename>
    struct accept3 : std::false_type
     { };
    
    template <template <typename...> class C, typename X, typename Y, typename Z>
    struct accept3<C<X, Y, Z>> : std::true_type
     { };
    
    template <typename C>
    std::enable_if_t<accept2<C>{} && !accept3<C>{}> foo (C const &)
     { }
    
    int main()
     {
       std::vector<int> v;
       std::map<int,int> m;
    
       foo(v);   // compile
       //foo(m); // compilation error
     }