Search code examples
c++language-lawyervariadic-templatestemplate-argument-deduction

How to explicitly specify template arguments for multiple parameter packs


I am trying to understand the following code:

template <class...Ts, class...Us> void f(void) {};

int main() {
  f<int, char, float, double>();
}

I don't know how template argument deduction deduces Ts and Us. In this answer, I learned that parameter packs are greedy, so Ts will match all specified arguments: [ Ts = int, char, float, double ]. But what about Us: it has not been deduced yet? or what happens in this case?

I expect that the compiler throws a deduction error since it cannot figure out what Us is expanded to.

Can I somehow, in the template argument-list, tell the compiler that I need Ts to be int, char, and Us to be float, double? how I can do that? I mean, can the call expression be something like that:

f<{int, char}, {float, double}>();

I found out that when I edit the above code to be as follows, this will fit my needs:

template <class...> struct S{};
template <class...Ts, class...Us> void g(S<Ts...>, S<Us...>)
{
};

int main() {

  S<int, char> s1;
  S<float, double> s2;

  g(s1 ,s2);
}

But I still need to understand why in this case Ts is [int, char], and Us is [float, double].

What I think in this case is that: Since parameter packs are greedy, template argument deduction deduces Ts as [S<int, char>, S<float, double>] and Us is left off un-deduced or just empty. Right?

This makes me think that

  1. First, [int, char] gets substituted in the place of Ts... in the first function parameter S<Ts..>, so it gets expanded into S<int, char>. Then [float, double] gets substituted in the place of Us... in the second function parameter S<Us..>, so it gets expanded into S<float, double>.

  2. Second, template argument deduction deduces Ts as [int, char] and Us as [float, double].

Is my understanding correct in this case?


Solution

  • Regarding the second part of the question (second example).

    Template argument deduction deduces parameter packs Ts as [int, char], and Us as [ float, double ] the same way it deduces T if you have the following:

    template <class> struct B {};
    template <class T> void h(B<T>)
    

    .. and you call h(B<int>()), template argument deduction is applied as follows:

    P = B<T>, A = B<int> -> [ T = int ] not [ T = B<int> ]
    

    The same is applied in the second example:

    P1 = S<Ts...>, A1 = S<int, char> --> [ Ts = int, char ];
    P2 = S<Us...>, A2 = S<float, double> --> [ Us = float, double ];