A Bite of Template in C++ (3)

In the post A Bite of Template in C++ (1), I mentioned a constraint for template member functions in class that explicit specialization is not allowed. However, I realized there are more constraints on function templates later when I wanted to make two template varients with slightly difference on only one member function. I tried specializing member function template, as well as inheriting a template member function from base class. Both of them failed due to some constraints. Briefly, the constraints are 1) partial specialization is not allowed for function template, 2) using declaration plus template disambiguator is not the right way to inherit a template member function from a template class.

Constraint 1: No Partial Specialization for Function Template

Issue of functions: overloading vs. specialization

Class template has no constraint on specializations, both partial specialization and explicit specialization are allowed, while that is not true for function template: partial specialization of function template is always illegal. As we know, in C++ functions could be overloaded, and compiler would look at the signatures trying to figure out the most match variant to use. So could function template be overloaded. This is an important difference between function template and class template. Although overloading uses function signature and template uses template parameter list to distinguish variants, there still exist chances to cause conflict. Here comes an example of conflict between function overloading and template specialization:

In the above example, because there exists an overloading of the function template, it becomes unclear which is the base template for the specialization [3] f(int*). You might think it does not matter, since [3] f(int*) is called whatever by the statement f(pi) in the main function. After all, [3] f(int*) is the candidate that matches f(pi) the most. Then the following example would surprise you:

It seems [5] g(double*) should be the candidate that matches g(pd) the most, but why [6] g(T*) is called instead? The reason is that the compiler narrows down to the best choice to it step by step, and resolves overloading first, then specialization. In the example, [5] g(double*) appears before [6] g(T*), so actually it is a specialization of [4] g(T). When compiling the example, the compiler does not find [5] g(double*) at one shoot. Among two base templates, [4] g(T) and [6] g(T*), the compiler chooses the latter for g(pb), because pb is a pointer. Next, the compiler goes through all specializations of [6] g(T*), while there is only one, [7] g(int*), in the example and is not appliable for g(pb). Finally, base template [6] g(T*) is used to instantiate a function for g(pb).
That makes sense for the print out of these two examples.

Better not to specialize function

As described in the previous section, function specialization could lead to confusion when used with overloading at the same time. The author of the article Why Not Specialize Function Templates? suggests to replace function template specialization with overloading, which seems to be powerful enough. Here comes two morals proposed by the author for users and function designers respectively:

Moral #1: If you want to customize a function base template and want that customization to participate in overload resolution (or, to always be used in the case of exact match), make it a plain old function, not a specialization. And, if you do provide over loads, avoid also providing specializations.
Moral #2: If you’re writing a function base template, prefer to write it as a single function template that should never be specialized or overloaded, and then implement the function template entirely as a simple handoff to a class template containing a static function with the same signature. Everyone can specialize that – both fully or partially, and without affecting the results of overload resolution.

To elaborate moral 1, we can turn the second example in the previous section into:

And an example to elaborate moral 2:

Moral 2 is like the adapter design pattern, and transfer the function specialization to struct specialization.

Constraint 2: Cannot Inherit a Template Member Function like a Type

Using declaration is necessary to inherit from a template

If there is a typedef claiming a new type alias in a base class or struct, as long as it is not in the private section, derived class or struct is able to access the new type alias. That is to say inheritance happens to the types as well. However, this is not always true for template inheritance. The exception occurs when a template class inherits from another template class. See the following example:

The struct Derived3 would fail the compilation of the above code. My explanation is that struct Derived3 does not trigger the instantiation of struct Base until the instantiation of itself. However, the compiler checks the definition of struct Derived3 before instantiating it. Without explicitly telling the compiler that num_t, type() and num_ are from struct Base inherited, the compiler fails at name looking up.
In the above example, we can also see the using declarations in struct Derived5 help it get rid of the issue.

Complicated dependent name looking up put a constraint on disambiguator

As introduced in my previous post A Bite of Template in C++ (2), typename and template are two disambiguators that help compilers resolve dependent names in template. In the example we just discussed, typename is used together with using to help struct Derived5 inherit a type alias num_t from struct Base. Naturally, the goal I want to achieve is to inherit a template member function from a template struct or class. I supposed template with using should be able to make this, so I tried:

This failure made me realized there is a constraint on disambiguators. After search, I only found several related sentences in Dependent names.

The keyword typename may only be used in this way before qualified names (e.g. T::x), but the names need not be dependent.

To my understanding, this means typename obj.x or typename inner_t::T are illegal (those are actually covered by the first and second parts of my previous post A Bite of Template in C++ (2) respectively).

The keyword template may only be used in this way after operators :: (scope resolution), -> (member access through pointer), and . (member access).

I doubt the latter two, -> and ., have more limitation than ::. For example, should not be used on a template type, see the counterexample I found in previous post A Bite of Template in C++ (2).
In addition, I guess as the same as disambiguator typename, the name cannot be dependent, struct Derived1 fails because same_type as the name of a template member function is a dependent name (depends on T). One clue is all valid template examples are like struct Derived3 having the template specified to a certain type. Nevertheless, I tried struct Derived2, but using a template specialization does not help.

Non-dependent names are binded to the best match by their definition, even though there is better match apears after the definition, the binding does not change, so known as binding rule. However, dependent names postpone binding until instantiation, so the compiler does looking up twice for dependent names: one at definition, one at instantiation. Such rules have already made the resolution of dependent names complicated. Perhaps, explicitly creating an alias for a dependent template name by using is forbiddon to keep the problem simple.

The way to inherit template member function is actually simple

There are still ways for derived class or struct to access template member functions in the base class or struct. The following code shows two approaches:

  1. make the base a specialization of the base template;
  2. let the template member function do type deduction according to the function parameter list rather than template parameter list;

In the function main(), I made a call obj1.same_type<int>() on purpose, so that we are sure actually struct Derived1 inherits same_type() as a template, but without a disambiguator, same_type() is not recognized as a template until instantiation.

Future Work

The post An introduction to C++ templates starts a series of 9 posts talking about the template. Definitely, I need more posts to cover more knowledge of template.

Credits