Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 433
ChronoTrigger Avatar asked Mar 04 '23 02:03

ChronoTrigger


1 Answers

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
 }
like image 184
max66 Avatar answered Mar 16 '23 03:03

max66