A Bite of Template in C++ (3)
05 Jul 2018 Languages C/C++ metaprogramming inheritanceIn 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:
- make the base a specialization of the base template;
- 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
- Why Not Specialize Function Templates? - It has surprising examples showing the interference between function overloading and template specialization.
- Dependent names - Detailed explanation on name lookup and the only sentences I found that restricts disambiguator in certain cases.
- Using-declaration - The example in its notes clearly shows the constraint.
- An introduction to C++ templates - From this one, the author makes a series of posts talking about templates of functions, classes, template inheritance, polymorphism, variadic and so on. Much more organized and practical than my posts.
- Public, Protected and Private Inheritance in C++ Programming - Nice article that explains different inheritance access control clearly.