Search code examples
c++templatespartial-specialization

Compiler errors on partial template speciailzation (c++)


I am trying to do a simple partial template specialization, but I get errors on g++4.4.7, g++4.8.5, clang++3.8.0. Whenever I mention compiler(s) error, I mean the output of all of these, as they always agree. I am using C++03, compiling without any option.

The code:

#include <iostream>

template <typename T, typename X, typename G>
struct A {};

template <typename T, typename X>
struct A<T, X, void> { A() : n(1) {} X n; T b; };

template <typename X>
struct A<X, void, void> { A() : n(2) {} X n; };

int main() {
  A<int, float> one;
  A<int> two;
  std::cout << one.n << " | " << two.n << "\n";
  return 0;
}

Question 1: This code fails to compile. The compilers say that A<int, float> and A<int> are wrong as A requires 3 templates parameters. Why?

If I change the original declaration to

template <typename T, typename X = void, typename G = void>
struct A {};

The code compiles and the output is: 1 | 2.

What happens is that the compiler in a first step matches one and two type to the not specialized A, but then it correctly decides to use the code of the partially specialized class one would expect it to use. But it should not need the defaults.

I then decide to change the last partial specialization switching the first and second parameter:

template <typename X>
struct A<void, X, void> { A() : n(2) {} X n; };

I would expect this to change nothing, but the compilers disagree. The clearest output between the 3 is here reported:

a.cpp:7:40: error: field has incomplete type 'void'
struct A<T, X, void> { A() : n(1) {} X n; T b; };
                                   ^
a.cpp:14:10: note: in instantiation of template class 'A<int, void, void>'    requested here
  A<int> two;
         ^
1 error generated.

Question 2: Why are the compilers considering the two variable an instance of the partial specialization of A that specializes only one argument?

Note that is the "2nd matching", because if I only use 1 default template argument, the compiler will go back to complaining about the fact that 3 template parameters are needed.

Thanks.


Solution

  • Question 1: This code fails to compile. The compilers say that A<int, float> and A<int> are wrong as A requires 3 templates parameters. Why?

    Because A requires 3 template parameters. You declared A as:

    template <typename T, typename X, typename G>
    struct A {};
    

    There is no two- or one-template parameter version of A. There are versions specialized on some of the types being void, but that's still a parameter - not an absence of parameter.

    When you add the defaults, then A<int, float> evaluates as A<int, float, void>, which is a valid instantiation - and picks the specialization which sets n to 1.

    You're misunderstanding how specialization works. Specialization doesn't change the number of template parameters. It's just a way of adding special functionality depending on what the template parameters end up being.

    Question 2: Why are the compilers considering the two variable an instance of the partial specialization of A that specializes only one argument?

    We have three choices

    template <T, X, G>       struct A; // the primary
    template <T, X, void>    struct A; // (1)
    template <void, X, void> struct A; // (2)
    

    When we instantiate A<int>, that is the same as A<int, void, void> when we add in the default parameters. That does not match (2) - because that one requires the first parameter to be void and yours is int. (1) is a better match than the primary since it's more specialized. But then, (1) has a member of type X and in this case X is deduced as void (from the default parameter), and that's not allowed.