4

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)?

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • 1
    What would be the use of such a specialization? – Jeff Garrett Nov 08 '20 at 17:17
  • 2
    @JeffGarrett does it have to be useful? Its a follow up of [this question](https://stackoverflow.com/q/64737968/4117728) (but I don't think it adds much context). Consider some machinery that instantiats templates for different combinations of template parameters, then you want to know the arity. A testing framework for templates is what comes to my mind, but I really don't have a concrete case at hand. – 463035818_is_not_an_ai Nov 08 '20 at 17:33
  • 2
    @JeffGarrett or much simpler. Given a template `T` write a trait that unambiuously determines if `T` is a variadic template. One can simply try to instantiate it with different number of parameters, but the possibility of defaults makes it difficult to be certain about variadicness – 463035818_is_not_an_ai Nov 08 '20 at 17:36
  • Neither clang or gcc compiles this with c++17. – Kostas Nov 29 '20 at 02:29
  • @Kostas do you know why? I would have to do some research, perhaps open another question. However, the example `foo` isn't essential for the question, as anyhow I am asking for something to replace `foo`. – 463035818_is_not_an_ai Nov 30 '20 at 17:19

2 Answers2

2

Very interesting question. Unfortunately the answer is No. There is no general way to determine if a template had a template parameter pack or just a bunch of regular template parameters with or without defaults.

The reason is that non-variadic templates can bind to variadic template template parameters and the concrete types of a template can bind to a template parameter pack.

So effectively the information is not available via deduction/specialization. And in general this is good - without this feature variadic templates would lose much of their power.

But if we could limit the maximum length of template arguments we could write a trait with a bunch of template specializations. This works because of partial ordering (as you have shown in your question): godbolt

bloody
  • 1,131
  • 11
  • 17
Bernd
  • 2,113
  • 8
  • 22
1

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.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • This is awesome. It was a rather academical question. I had hit a wall and was afraid that I have reconsdier my whole mental image of variadic templates. Now I am curious, did you actually use something like this for something? – 463035818_is_not_an_ai Nov 29 '20 at 18:37
  • 1
    @idclev463035818 I don't believe so; it took some thinking about the visible differences between a 0-argument variadic and default-argument class template instantiation to come up with. That said, I am familiar with the quirks of decomposing class template instantiations, but mostly from the angle of determining which class template they are an instantiation of. – ecatmur Nov 29 '20 at 19:06