This question already has answers here:
How does `void_t` work
(3 answers)
Closed 4 years ago.
I have a program that is as follows. There is a base template struct X and a partial specialisation with SFINAE.
template <typename T, typename U = void>
struct X{
X() {
std::cout << "in 1" << std::endl;
};
};
template <typename T>
struct X< T, std::enable_if_t<std::is_integral_v<T>> > {
X() {
std::cout << "in 2" << std::endl;
};
};
int main() {
X<int> x;
}
When running the program in 2 is printed.
Why is it that the second specialization is chosen over the first since both of them effectively declare a struct X<int, void>. What makes std::enable_if_t<std::is_integral_v<T>> more specialized than a default template type argument as shown in the base template?
Why does the default type argument of the base template have to be the same as the type defined by the partial specialization for the partial specialization to be called and in 2 to be printed.
Why does changing to std::enable_if_t<std::is_integral_v<T>, bool> cause the base template in 1 to be called?
The answers to your questions lie in Template Partial Ordering. This is the mechanism the compiler uses to determine which template is the best fit (be it a function template overload, or in your case, a class template specialization).
In brief, your generic template implementation has 2 parameters T and U, whereas your SFINAE specialization have only the T parameter, while the second is deduced from T. It is therefore more specialized than the general case and in the end, when you refer to X<int, void>, the specialization is chosen.
Now question 2. Suppose we replace the enable_if parameter with bool instead of void. Now our specialization will be X<int, bool> instead of X<int, void>, so when you refer to X<int>, i.e. X<int, void>, it doesn't match the specialization anymore because those are 2 different types.
1) [...] What makes std::enable_if_t> more specialised than a default template type argument as shown in the base template?
So do you know that, if two template match, the more specialized is selected.
Well... the second one is more specialized because if X matches the specialization (so if X is an integral type), it's that matches also the generic version.
But exist couples of types (by example: std::string, void) that matches the generic version and doesn't matches the specialization.
So the specialization is more specialized that the generic version, so is preferred when both template match.
Why does the default type argument of the base template have to be the same as the type defined by the partial specialisation for the partial specialisation to be called and in 2 to be printed. Why does changing to std::enable_if_t, bool> cause the base template in 1 to be called?
You have to understand how works the trick of the default type value.
You have that the generic version that is
template <typename T, typename U = void>
struct X;
so writing X<int> x, you're writing X<int, void> x; and surely matches the generic version.
The specialization is
template <typename T>
struct X< T, std::enable_if_t<std::is_integral_v<T>>>;
Question: X<int, void> matches X< T, std::enable_if_t<std::is_integral_v<T>>> ?
Answer: yes, because int is integral, so std::enable_if_t<std::is_integral_v<T>> is substituted with void.
Suppose now that the generic specialization become
template <typename T>
struct X< T, std::enable_if_t<std::is_integral_v<T>, bool>>
We have that X<int> x is again X<int, void> x and matches again the generic version.
Question: X<int, void> matches also X< T, std::enable_if_t<std::is_integral_v<T>, bool>> ?
Answer: no, because std::enable_if_t<std::is_integral_v<T>, bool> become bool and X<int, void> doesn't matches X<int, bool>
So the generic version is the only one that matches and is selected.
Related
So yet another question in this saga. Guillaume Racicot has been good enough to provide me with yet another workaround so this is the code I'll be basing this question off of:
struct vec
{
double x;
double y;
double z;
};
namespace details
{
template <typename T>
using subscript_function = double(*)(const T&);
template <typename T>
constexpr double X(const T& param) { return param.x; }
template <typename T>
constexpr double Y(const T& param) { return param.y; }
template <typename T>
constexpr double Z(const T& param) { return param.z; }
}
template <typename T, typename = void>
constexpr details::subscript_function<T> my_temp[] = { &details::X<T>, &details::Y<T> };
template <typename T>
constexpr details::subscript_function<T> my_temp<T, enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>>[] = { &details::X<T>, &details::Y<T>, &details::Z<T> };
int main() {
vec foo = { 1.0, 2.0, 3.0 };
for(const auto i : my_temp<decltype(foo)>) {
cout << (*i)(foo) << endl;
}
}
The problem seems to arise in my specialization when I return something other than void. In the code above for example, enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T> prevents specialization, while simply removing the last argument and allowing enable_if to return void allows specialization.
I think this points to my misunderstanding of what is really happening here. Why must the specialized type always be void for this to work?
Live Example
Not sure to understand what you don't understand but...
If you write
template <typename T, typename = void>
constexpr details::subscript_function<T> my_temp[] = { &details::X<T>, &details::Y<T> };
template <typename T>
constexpr details::subscript_function<T> my_temp<T, enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>>[] = { &details::X<T>, &details::Y<T>, &details::Z<T> };
you have a first, main, template variable with two templates: a type and a type with a default (void).
The second template variable is enabled when std::enable_if_t is void.
What's happen when you write
for(const auto i : my_temp<decltype(foo)>)
?
The compiler:
1) find my_temp<decltype(foo)> that has a single template parameter
2) look for a matching my_temp template variable
3) find only a my_temp with two template parameters but the second has a default, so
4) decide that my_temp<decltype(foo)> can be only my_temp<decltype(foo), void> (or my_temp<vec, void>, if you prefer)
5) see that the main my_temp matches
6) see that the my_temp specialization doesn't matches because
enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>
is T (that is vec), so could match only my_temp<vec, vec> that is different from my_temp<vec, void>.
7) choose the only template variable available: the main one.
If you want that the specialization is enabled by
enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>
you should use T
// ..............................V T! not void
template <typename T, typename = T>
constexpr details::subscript_function<T> my_temp[] = { &details::X<T>, &details::Y<T> };
as default for second template type in the main template variable.
Off Topic suggestion: better use std::declval inside the std::is_floating_point_v test; I suggest
std::enable_if_t<std::is_floating_point_v<decltype(details::X(std::declval<T>()))>>
How template specialization works:
There is a primary specialization. This one basically defines the arguments and defaults.
template <typename T, typename = void>
This is the template part of your primary specialization. It takes one type, then another type that defaults to void.
This is the "interface" of your template.
template <typename T>
[...] <T, enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>> [...]
here is a secondary specialization.
In this case, the template <typename T> is fundamentally different. In the primary specialization, it defined an interface; here, it defines "variables" that are used below.
Then we have the part where we do the pattern matching. This is after the name of the template (variable in this case). Reformatted for sanity:
<
T,
enable_if_t
<
is_floating_point_v
<
decltype
(
details::X(T())
)
>,
T
>
>
now we can see the structure. There are two arguments, matching the two arguments in the primary specialization.
The first one is T. Now, this matches the name in the primary specialization, but that means nothing. It is like calling a function make_point(int x, int y) with variables x,y -- it could be y,x or m,n and make_point doesn't care.
We introduced a completely new variable T in this specialization. Then we bound it to the first argument.
The second argument is complex. Complex enough that it is in a "non-deduced context". Typically, template specialization arguments are deduced from the arguments passed to template as defined in the primary specialization; non-deduced arguments are not.
If we do some_template< Foo >, matching a type T against Foo gets ... Foo. Pretty easy pattern match. Fancier pattern matches are permitted, like a specialization that takes a T*; this fails to match against some_template<int>, but matches against some_template<int*> with T=int.
Non-deduced arguments do not participate in this game. Instead, the arguments that do match are plugged in, and the resulting type is generated. And if and only if that matches the type passed to the template in that slot does the specialization match.
So lets examine what happens we pass vec as the first argument to my_temp
First we go to the primary specialization
template<typename T, typename=void>
my_temp
now my_temp<vec> has a default argument. It becomes my_temp<vec,void>.
We then examine each other specialization to see if any of them match; if none do, we stay as the primary specialization.
The other specialization is:
template<typename T>
[...] my_temp<
T,
enable_if_t
<
is_floating_point_v
<
decltype
(
details::X(T())
)
>,
T
>
>[...]
with [...] for stuff that doesn't matter.
Ok, the first argument is bound to T. Well, the first argument is vec, so that is easy. We substitute:
template<typename T>
[...] my_temp<
vec,
enable_if_t
<
is_floating_point_v
<
decltype
(
details::X(vec())
)
>,
vec
>
>[...]
then evaluate:
template<typename T>
[...] my_temp<
vec,
enable_if_t
<
is_floating_point_v
<
double
>,
vec
>
>[...]
and more:
template<typename T>
[...] my_temp<
vec,
enable_if_t
<
true,
vec
>
>[...]
and more:
template<typename T>
[...] my_temp<
vec,
vec
>[...]
ok, remember we where trying to match against my_temp<vec,void>. But this specialization evaluated to my_temp<vec,vec>, and those don't match. Rejected.
Remove the ,T from enable_if, or make it ,void (same thing), and the last line of the above argument becomes my_temp<vec,void> matches my_temp<vec,void>, and the secondary specialization is chosen over the primary one.
It is confusing. The same syntax means fundamentally different things in primary specialization and secondary ones. You have to understand pattern matching of template arguments and non-deduced contexts.
And what you usually get is someone using it like a magic black box that you copy.
The magic black box -- the patterns -- are useful because they mean you don't have to think about the details of how you got there. But understanding pattern matching of template arguments, deduced and non-deduced contexts, and the differences between primary and secondary specializations is key to get why the black box works.
With
struct vec
{
double x;
double y;
double z;
};
and
template <typename T>
constexpr double X(const T& param) { return param.x; }
we'll find out that
is_floating_point_v<decltype(details::X(T()))
evaluates to true (unless you're going to specialise X for vec not to return floating point...).
So we actually have:
template <typename T>
constexpr details::subscript_function<T> my_temp<T, enable_if_t<true, T>>[]
= { /*...*/ };
or shorter:
template <typename T>
constexpr details::subscript_function<T> my_temp<T, T>[]
= { /*...*/ };
(if it exists at all, of course...). Explicitly choosing one or the other:
my_temp<decltype(foo), void>
my_temp<decltype(foo), int>
my_temp<decltype(foo), double>
all match the main template, but none of the specialisation.
my_temp<decltype(foo), decltype(foo)>
now does match the specialisation (which exists because of X(foo) returning double...).
Finally back to my_temp<decltype(foo)> – well, only one template parameter given. Which is the type of the second one? The default parameter tells you (or better: the compiler), it is void. And according to above...
So if you want to match the specialisation, either this one needs void as type of second template parameter (as you discovered already) or you change the default in the non-specialized template to being equal to first template parameter (typename T, typename = T).
Actually, you could select any type for the default and the specialisation, as long as you choose the same for both (e. g. twice int, std::string, MyVeryComplexCustomClass, ...).
I have the following code:
#include <iostream>
template <class T, typename U = void> class A;
template <class T>
class C
{
public:
typedef T Var_t;
};
template <class T>
class B : public C<T>
{
};
template <class T>
class A<B<T>>
{
public:
A() { std::cout << "Here." << std::endl; }
};
template <class T>
class A<T, typename std::enable_if<
std::is_base_of<C<typename T::Var_t>, T>::value>
::type>
{
public:
A() { std::cout << "There." << std::endl;}
};
int main()
{
A<B<int>> a;
return 0;
}
When the compiler tries to instantiate the second partial specialization with the parameter B<int>, std::is_base_of<C<int>, B<int>>::value is true, and therefore the std::enable_if<...>::type returns void (the default type if one isn't specified). This causes an "ambiguous partial specialization" error as the compiler can't decide between the first and second partial specializations. So far, so good. However, when I replace the code within the std::enable_if to simply be true (i.e., the second partial specialization is just template <class T> class A<T, typename std::enable_if<true>::type>), the code compiles and runs. It outputs "Here", indicating the first specialization was chosen.
My question is: If they both evaluate to void in the end, why does the behavior of std::enable_if<true>::type differ to that of std::enable_if<std::is_base_of<...>::value>::type?
This behavior has been tested and verified on Ideone here.
In the std::enable_if<true>::type case your code defines two specialisations of the class A namely:
A<B<T>, void>
A<T, std::enable_if<true>::type>.
Those two specialisations are quite distinct from each other. The first specialisation is narrowly focused on the type B<T> while the second specialisation is more general fitting any type at all. Also, in the second specialisation the std::enable_if expression does not depend on T in any way.
For any declaration A<X> a; the type X will either match B<something> or not. If it matches B<something> then the first specialisation will be used because it is "more specialised". If X does not match B<something> then the second, more general specialisation will be used. Either way you don't get the ambiguous error.
For more details see the discussion of Partial Ordering in partial template specialization
Now let's consider the std::enable_if<std::is_base_of<...>::value>::type case.
You still have two specialisations but the second specialisation is now conditional on the enable_if which in turn depends on the parameter T.
A<B<T>, void>
A<T, std::enable_if<...>>.
The type B<int> now matches both specialisations (to some equal extent). Obviously it matches the A<B<T>>, void> specialisation but it also matches the A<T, std::enable_if...>> specialisation because B<int> is a type which satisfies the conditions imposed by the std::enable_if expression.
That gives you two equally valid specialisations that are candidates for your declaration of the variable a and so you get the "ambiguous partial specialization" error.
It might help make all this a bit more concrete if you added two more declarations to main
A<C<int>> x;
A<int> y;
In the std::enable_if<true> case this will compile and both declarations will call the "there" constructor.
In the more complex case the declaration of x will compile and invoke the "there" constructor but the declaration of y will get a compiler error.
There is no int::Var_t so the std::enable_if expression will get a substitution failure and SFINAE will hide that specialisation. That means there won't be any specialisation that fits int and you'll get the error aggregate ‘A<int> y’ has incomplete type and cannot be defined
While trying to implement a few things relying on variadic templates, I stumbled accross something I cannot explain. I boiled down the problem to the following code snippet:
template <typename ... Args>
struct A {};
template <template <typename...> class Z, typename T>
struct test;
template <template <typename...> class Z, typename T>
struct test<Z, Z<T>> {
static void foo() {
std::cout << "I'm more specialized than the variadic spec, hehe!" << std::endl;
}
};
template <template <typename...> class Z, typename T, typename ... Args>
struct test<Z, Z<T, Args...>> {
static void foo() {
std::cout << "I'm variadic!" << std::endl;
}
};
int main() {
test<A, A<int>>::foo();
}
Under gcc, it produces an error because it considers both specializations to be equally specialized when trying to instantiate test<A, A<int>>:
main.cpp: In function 'int main()':
main.cpp:25:24: error: ambiguous template instantiation for 'struct test<A, A<int> >'
test<A, A<int>>::foo();
^~
main.cpp:11:12: note: candidates are: template<template<class ...> class Z, class T> struct test<Z, Z<T> > [with Z = A; T = int]
struct test<Z, Z<T>> {
^~~~~~~~~~~~~
main.cpp:18:12: note: template<template<class ...> class Z, class T, class ... Args> struct test<Z, Z<T, Args ...> > [with Z = A; T = int; Args = {}]
struct test<Z, Z<T, Args...>> {
However, clang deems the first specialization "more specialized" (through partial ordering: see next section) as it compiles fine and prints:
I'm more specialized than the variadic spec, hehe!
A live demo can be found on Coliru. I also tried using gcc's HEAD version and got the same errors.
My question here is: since these two well-known compilers behave differently, which one is right and is this piece of code correct C++?
Standard interpretation (C++14 current draft)
From the sections §14.5.5.1 and $14.5.5.2 of the C++14 standard draft, partial ordering is triggered to determine which specialization should be chosen:
(1.2) — If more than one matching specialization is found, the partial order rules (14.5.5.2) are used to determine
whether one of the specializations is more specialized than the others. If none of the specializations
is more specialized than all of the other matching specializations, then the use of the class template is
ambiguous and the program is ill-formed.
Now according to §14.5.5.2, the class template specializations are transformed into function templates through this procedure:
For two class template partial specializations, the first is more specialized than the second if, given the
following rewrite to two function templates, the first function template is more specialized than the second
according to the ordering rules for function templates (14.5.6.2):
(1.1) — the first function template has the same template parameters as the first partial specialization and has
a single function parameter whose type is a class template specialization with the template arguments
of the first partial specialization, and
(1.2) — the second function template has the same template parameters as the second partial specialization
and has a single function parameter whose type is a class template specialization with the template
arguments of the second partial specialization.
Therefore, I tried to reproduce the issue with the function template overloads that the transformation described above should generate:
template <typename T>
void foo(T const&) {
std::cout << "Generic template\n";
}
template <template <typename ...> class Z, typename T>
void foo(Z<T> const&) {
std::cout << "Z<T>: most specialized overload for foo\n";
}
template <template <typename ...> class Z, typename T, typename ... Args>
void foo(Z<T, Args...> const&) {
std::cout << "Z<T, Args...>: variadic overload\n";
}
Now trying to use it like this:
template <typename ... Args>
struct A {};
int main() {
A<int> a;
foo(a);
}
yields a compilation error [ambiguous call] in both clang and gcc: live demo. I expected clang would at least have a behavior consistent with the class template case.
Then, this is my interpretation of the standard (which I seem to share with #Danh), so at this point we need a language-lawyer to confirm this.
Note: I browsed a bit LLVM's bug tracker and could not find a ticket for the behavior observed on function templates overloads in this question.
From temp.class.order:
For two class template partial specializations, the first is more specialized than the second if, given the following rewrite to two function templates, the first function template is more specialized than the second according to the ordering rules for function templates ([temp.func.order]):
Each of the two function templates has the same template parameters as the corresponding partial specialization.
Each function template has a single function parameter whose type is a class template specialization where the template arguments are the corresponding template parameters from the function template for each template argument in the template-argument-list of the simple-template-id of the partial specialization.
The order of:
template <template <typename...> class Z, typename T>
struct test<Z, Z<T>> {
static void foo() {
std::cout << "I'm more specialized than the variadic spec, hehe!" << std::endl;
}
};
template <template <typename...> class Z, typename T, typename ... Args>
struct test<Z, Z<T, Args...>> {
static void foo() {
std::cout << "I'm variadic!" << std::endl;
}
};
depends on the order of:
template <template <typename...> class Z, typename T>
void bar(test<Z, Z<T>>); // #1
template <template <typename...> class Z, typename T, typename ... Args>
void bar(test<Z, Z<T, Args...>>); // #2
From [temp.func.order]:
Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the function type. The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process.
To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs ([temp.variadic]) thereof) synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template.
Using the transformed function template's function type, perform type deduction against the other template as described in [temp.deduct.partial].
By those paragraph, for any function transformed from any synthesized template Z0 and type T0, which can form #1, we can do type deduction with #2. But functions transformed from #2 with fictitious template Z2 with any type T2 and any non-empty set of Args2 can't be deduced from #1. #1 is obviously more specialized than #2.
clang++ is right in this case.
Actually, this one and this one are failed to compile (because of ambiguous) in both g++ and clang. It seems like both compilers have hard time with template template parameters. (The latter one is clearly ordered because its order is the same of no function call).
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.
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.
This is from the C++ Standard Library xutility header that ships with VS2012.
template<class _Elem1,
class _Elem2>
struct _Ptr_cat_helper
{ // determines pointer category, nonscalar by default
typedef _Nonscalar_ptr_iterator_tag type;
};
template<class _Elem>
struct _Ptr_cat_helper<_Elem, _Elem>
{ // determines pointer category, common type
typedef typename _If<is_scalar<_Elem>::value,
_Scalar_ptr_iterator_tag,
_Nonscalar_ptr_iterator_tag>::type type;
};
Specifically what is the nature of the second _Ptr_cat_helper declaration? The angle brackets after the declarator _Ptr_cat_helper make it look like a specialization. But instead of specifying full or partial types by which to specialize the template it instead just repeats the template argument multiple times.
I don't think I've seen that before. What is it?
UPDATE
We are all clear that the specialization applies to an instantiation of the template where both template arguments are of the same type, but I'm not clear on whether this constitutes a full or a partial specialization, or why.
I thought a specialization was a full specialization when all the template arguments are either explicitly supplied or are supplied by default arguments, and are used exactly as supplied to instantiate the template, and that conversely a specialization was partial either, if not all the template parameters were required due to the specialization supplying one or more (but not all) of them, and/or if the template arguments were used in a form that was modified by the specialization pattern. E.g.
A specialization that is partial because the specialization is supplying at least one, but not all, of the template arguments.
template<typename T, typename U>
class G { public: T Foo(T a, U b){ return a + b; }};
template<typename T>
class G<T, bool> { public: T Foo(T a, bool b){ return b ? ++a : a; }};
A specialization that is partial because the specialization is causing the supplied template argument to be used only partially.
template<typename T>
class F { public: T Foo(T a){ return ++a; }};
template<typename T>
class F<T*> { public: T Foo(T* a){ return ++*a; }};
In this second example if the template were instantiated using A<char*> then T within the template would actually be of type char, i.e. the template argument as supplied is used only partially due to the application of the specialization pattern.
If that is correct then wouldn't that make the template in the original question a full specialization rather than a partial specialization, and if that is not so then where is my misunderstanding?
It is a partial class template specialization for the case when the same type is passed for both parameters.
Maybe this will be easier to read:
template<typename T, typename U>
struct is_same : std::false_type {};
template<typename T>
struct is_same<T,T> : std::true_type {};
EDIT:
When in doubt whether a specialization is an explicit (full) specialization or a partial specialization, you can refer to the standard which is pretty clear on this matter:
n3337, 14.7.3./1
An explicit specialization of any of the following:
[...]
can be declared by a declaration introduced by template<>; that is:
explicit-specialization:
template < > declaration
and n3337, 14.5.5/1
A primary class template declaration is one in which the class
template name is an identifier. A template declaration in which the
class template name is a simple-template-id is a partial
specialization of the class template named in the simple-template-id. [...]
Where simple-template-id is defined in the grammar like this:
simple-template-id:
template-name < template-argument-list opt >
template-name
identifier
So, wherever there's template<>, it's a full specialization, anything else is a partial specialization.
You can also think about it this way: Full template specialization specializes for exactly one possible instantiation of the primary template. Anything else is a partial specialization. Example in your question is a partial specialization because while it limits the arguments to be of the same type, it still allows for indifinitely many distinct arguments the template can be instantiated with.
A specialization like this, for example
template<>
vector<bool> { /* ... */ };
is a full specialization because it kicks in when the type is bool and only bool.
Hope that helps.
And just a note I feel it's worth mentioning. I guess you already know - function templates can't be partialy specialized. While this
template<typename T>
void foo(T);
template<typename T>
void foo(T*);
might looks like a partial specialization of foo for pointers on the first glance, it is not - it's an overload.
You mention specifying "full or partial types" when performing specialization of a template, which suggests that you are aware of such language feature as partial specialization of class templates.
Partial specialization has rather extensive functionality. It is not limited to simply specifying concrete arguments for some of the template parameters. It also allows defining a dedicated version of template for a certain groups of argument types, based on their cv-qualifications or levels of indirection, as in the following example
template <typename A, typename B> struct S {};
// Main template
template <typename A, typename B> struct S<A *, B *> {};
// Specialization for two pointer types
template <typename A, typename B> struct S<const A, volatile B> {};
// Specialization for const-qualified type `A` and volatile-qualified type `B`
And it also covers specializations based on whether some template arguments are identical or different
template <typename A> struct S<A, A> {};
// Specialization for two identical arguments
template <typename A> struct S<A, A *> {};
// Specialization for when the second type is a pointer to the first one
As another, rather curios example, partial specialization of a multi-argument template can be used to fully override the main template
template <typename A, typename B> struct S<B, A> {};
// Specialization for all arguments
Now, returning to your code sample, partial specialization for two identical arguments is exactly what is used in the code you posted.