c++templatesvariadic-templatestemplate-meta-programmingtemplate-templates

How do nested templates get resolved in C++?


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?


Solution

  • 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.