When specialization of template class is allowed with more template arguments than it's been declared?

Viewed 50

The code is very simple and concise (YATC means yet another template class :))

template<typename T1, typename T2>
class YATC; /*declaration*/

template<typename T1>
class YATC<T1,T1> {}; 

template<typename T1, typename T2, typename T3>
class YATC<T1, YATC<T2, T3>> {};

int main()
{
    YATC<int, YATC<int, double>> yatc;
    return 0;
}

This black magic looks very disturbing for me.

  • I declared the class as a template class with two template arguments T1,T2;

  • I've expected that I can make specializations for YATC class based on idea that I can specificate definition of the class on two template arguments only;

  • I've found out that actually I can make specialization using infinite template arguments like typename T1, typename T2, typename T3 but with some interesting restrictions:

  • I cannot use std::common_type_t<T1, T2> as second parameter for YATC specialization. The compiler throws an error that T2 isn't used for YATC's specialization;

  • However, I can use them for using specialization with instance of some template class that receives these arguments (as YATC<T2, T3>);

  • But! I still cannot instantiate the class with YATC<int, int, double>, only YATC<int, YATC<int, double>>;

Why the such declaration is still valid but why I cannot instantiate the class with YATC<int, int, double>?

2 Answers

I've expected that I can make specializations for YATC class based on idea that I can specificate definition of the class on two template arguments only;

And this is correct.

I've found out that actually I can make specialization using infinite template arguments like typename T1, typename T2, typename T3 but with some interesting restrictions:

Yes... but still remain that YACT has only two template arguments

Take your second specialization

template<typename T1, typename T2, typename T3>
class YATC<T1, YATC<T2, T3>> {};

You use three template parameter but YACT receive only two template arguments:

  1. T1
  2. YACT<T2, T3>

The second one is something complex, and is expressed through YACT itself and a couple of template parameters of the specializations, but YACT<T2, T2> remain a single template argument, from the YACT point of view.

Substantially, declaring

template<typename T1, typename T2>
class YATC;

you declare that YACT receive exactly two types; nothing forbid you to construct your types in a very very complex way

//                1                                  2
//...vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv..vvvvvvvvvvvvvvvvvvvvvvvvv
YACT<std::tuple<YACT<int, long>>, char>, std::vector<std::string>>> foo;

and declare YACT specializations that use a lot of template parameter (not only types but also values and template-template), to express the couple the YACT types parameter. But YACT has to receive exaclty two types parameters. Complex as you will but exactly two.

I still cannot insatiate the class with YATC<int, int, double>, only YATC<int, YATC<int, double>>;

Exactly.

Because with

//    1    2      3
// ..VVV..VVV..VVVVVV
YATC<int, int, double>

you pass three types to YACT, but with

//    1           2
// ..VVV..VVVVVVVVVVVVVVVVV
YATC<int, YATC<int, double>>

you pass two types to YACT (YACT<int, double> is a single type).

Because YATC is still declared to take two template arguments, despite the specialization misleading you to think otherwise.

It may help to think of the specializations as to be themselves templated. To give you an idea of what I mean, consider:

template<typename T1, typename T2>
class YATC;

A specialization of YATC here is constructed using two types - T1 and T2.

template<typename T1, typename T2, typename T3>
class YATC<T1, YATC<T2, T3>> {};

Here, it is true that the specialization has three template arguments - T1, T2 and T3. However, YATC itself still only takes two template arguments - T1 and YATC<T2, T3>.

So, to answer your question directly, declaring template<typename T1, typename T2, typename T3> class YATC<T1, YATC<T2, T3>> does not actually declare YATC to be a template taking three arguments. The specialization itself, in a way, can be thought of as a template taking three arguments, though these arguments are always filled in implicitly when the specialization is constructed.

Related