Search code examples
c++templatesvariadic-templatestemplate-templates

How to match only variadic templates with a template template parameter?


Consider the following code:

#include <iostream>

template <template<class...> class C>
struct foo {
    foo() { std::cout << "base case\n";}
};

template <template<class> class C>
struct foo< C > {
    foo() { std::cout << "single param case\n";}
};

template <template<class,class> class C>
struct foo< C > {
    foo() { std::cout << "two param case\n";}
};

template <typename T> struct bar1 {};
template <typename T,typename U> struct bar2 {};
template <typename T,typename U,typename V> struct bar3 {};
template <typename...T> struct barN {};

int main() {
    foo<bar1> f;
    foo<bar2> g;
    foo<bar3> h;
    foo<barN> n;
}

Output is (gcc10.2@godbolt):

single param case
two param case
base case
base case

Suppose barX is given and that I have other templates with varying number of type parameters. Some variadic some not.

Is it possible to write a specialization that only matches the variadic template (barN in the above example)?


Solution

  • We can determine whether a class template that can be instantiated with 0 template arguments is genuinely variadic or (merely) has defaults for all its non-variadic template arguments, by counting the arguments to an 0-argument instantiation:

    template<class> constexpr unsigned argc_v;
    template<template<class...> class C, class... A> constexpr unsigned argc_v<C<A...>> = sizeof...(A);
    template<template<class...> class, class = void> constexpr bool is_variadic_v = false;
    template<template<class...> class C> constexpr bool is_variadic_v<C, std::void_t<C<>>> = argc_v<C<>> == 0;
    

    Then we can use this to build a set of specializations that respectively accept only variadic, 1-argument (with possible default) and 2-argument (with possible default/s) class templates:

    template<template<class...> class, class = std::true_type>
    struct foo;
    
    template<template<class...> class C>
    struct foo<C, std::bool_constant<is_variadic_v<C>>> {
        foo() { std::cout << "variable case\n"; }
    };
    
    template<template<class> class C>
    struct foo<C, std::bool_constant<!is_variadic_v<C> && argc_v<C<void>> == 1>> {
        foo() { std::cout << "single param case\n";}
    };
    
    template<template<class, class> class C>
    struct foo<C, std::bool_constant<!is_variadic_v<C> && argc_v<C<void, void>> == 2>> {
        foo() { std::cout << "two param case\n";}
    };
    

    I'm a bit disappointed that the latter argc_v tests are necessary (in C++20 mode); I think it's something to do with P0522 / CWG150.

    Demo.