I recently asked a question about determining whether an iterator points to a complex value at compile time and received an answer that works.
The question is here: How can I specialize an algorithm for iterators that point to complex values?
And the solution was a set of templates that determine whether one template is a specialization of another:
template <class T, template <class...> class Template>
struct is_specialization : std::false_type {};
template <template <class...> class Template, class... Args>
struct is_specialization<Template<Args...>, Template> : std::true_type {};
This does work, but I am really struggling to understand how this works. Particularly the nested template
within a template
is confusing to me. I'm also still fairly new to using variadic templates and it seems odd to have a variadic template with no type provided, for example: <class...>
instead of something like this <class... Args>
.
Can someone please break down this template and describe how it gets resolved?
You have to take in count that there are three types of template parameters:
1) types
2) non-types (or values)
3) template-templates
The first type is preceded by typename
(or class
)
template <typename T>
void foo (T const & t);
In the preceding example, T
is a type and t
(a classical function argument) is a value of type T
.
The second type of template parameter are values and are preceded by the type of the value (or auto
, starting from C++17, for a not specified type)
template <int I>
void bar ()
{ std::cout << I << std::endl; }
In the preceding example the I
template parameter is a value of type int
.
The third type is the most complex to explain.
Do you know (I suppose) that std::vector<int>
and std::vector<double>
are different types, but they have in common std::vector
, a template class.
A template-template parameter is a parameter that accept std::vector
, the template class without arguments.
A template-template parameter is preceded by a template
keyword, as in the following example
template <template <int> class C>
void baz ();
The template-template parameter C
in the preceding example is class (or struct) that require a single int
(value) template parameter.
So if you have a class
template <int I>
class getInt
{ };
you can pass getInt
, as template parameter, to baz()
baz<getInt>();
Now you should be able to understand your code:
template <class T, template <class...> class Template>
struct is_specialization : std::false_type {};
the is_specialization
struct is a template struct that receive, as template parameters, a type (T
) and a template-template Template
that accept classes/structs receiving a variadic number of type template parameters.
Now you have a specialization of is_specialization
:
template <template <class...> class Template, class... Args>
struct is_specialization<Template<Args...>, Template> : std::true_type {};
This specialization is selected when the first template parameter (Template<Args...>
) is a class based on the second (Template
).
An example: if you instantiate
is_specialization<std::vector<int>, std::map>
the main version (that inherit from std::false_type
) is selected because std::vector<int>
isn't based on std::map
.
But if you instantiate
is_specialization<std::vector<int>, std::vector>
the specialization (that inherit from std::true_type
) is selected because std::vector<int>
is based on std::vector
.