Reputation: 2500
Take as an example this code to determine the length of a type list:
template <class... Types>
class type_list {};
template <class TypeList>
struct type_list_length; // <---
template <template <class...> class type_list, class... Types>
struct type_list_length<TypeList<Types...>>
{
static constexpr std::size_t value = sizeof...(Types);
};
Why do we need the marked declaration? I tried to compile the code without it in several compilers but are always getting errors.
Upvotes: 1
Views: 66
Reputation: 66230
But why do we need the specialization in the first place?
Because you use the class
as follows
std::cout << type_list_length<type_list<int, long, long long>>::value;
// ...........................^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- template argument
or also
std::cout << type_list_length<std::tuple<int, long, long long>>::value;
// ...........................^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- template argumen
or in a similar way.
Observe the template argument: in both cases is a type; type_list<int, long, long long>
in first case, std::tuple<int, long, long long>
.
So you can't declare type_list_length
as receiving a template-template type
template <template <class...> class type_list, class... Types>
struct type_list_length // <--- doesn't work
{
static constexpr std::size_t value = sizeof...(Types);
};
because you should call it passing a template-template parameter followed by a variadic list of templates; I mean... you should use it as follows
std::cout << type_list_length<type_list, int, long, long long>::value;
std::cout << type_list_length<std::tuple, int, long, long long>::value;
but, this way, you loose the power of the class: extract and count the template parameter of the type parameter.
So you need first declare type_list_length
as receiving a type
template <typename> // no template parameter name needed here (not used)
struct type_list_length;
and then declare and define a specialization in case the received type is a template-template with arguments
template <template <typename...> class type_list, typename... Types>
struct type_list_length<TypeList<Types...>>
{ // ...................^^^^^^^^^^^^^^^^^^ the parameter is a type
static constexpr std::size_t value = sizeof...(Types);
};
Upvotes: 1
Reputation: 41110
The short answer is that without the primary template we cannot write a specialization.
The longer answer is: how would you extract Types...
from the instantiation of a templated type without a specialization? You cannot.
Here's an attempt:
template <template <class...> class type_list, class... Types>
struct type_list_length
{
static constexpr std::size_t value = sizeof...(Types);
};
We can do this:
type_list_length<type_list, int, double, float>::value
But not this:
using MyListType = type_list<int, double, float>;
type_list_length<MyListType>::value;
Because our template expects a template-template parameter and some types, so we're forced to accept just a single type to match MyListType
:
template <class T>
struct type_list_length
{
static constexpr std::size_t value = // ????;
};
But now we're faced with another issue. How do we assign value
? We need some way to extract the template arguments for MyListType
, or at least the count.
We need a way to match a single type and the arguments it is templated on. Hence, we need match just a single type AND its template parameters.
template <class TypeList>
struct type_list_length;
template <template <class...> class type_list, class... Types>
struct type_list_length<TypeList<Types...>>
{
static constexpr std::size_t value = sizeof...(Types);
};
The first (incomplete) type is our primary template. It allows us to start matching a single type, like MyListType
.
The second (complete) type is our specialization. it allows us to match a single type AND if it's a templated type, match the types used as template parameters for it.
By leaving the first type incomplete, we demonstrate our intention to ONLY allow the specialization to be valid.
Upvotes: 1