If you wanted to know just about everything about C++ templates then the book C++ Templates—The Complete Guide by David Vandevoorde and Nicolai Josuttis, (ISBN 0-201-73484-2) is a readable reference book you can use. Normally, discussing a book would appear in a book review. However, since the authors have done such a good job of describing C++ templates, I though the topic and the book deserved more complete coverage. You may recall Nicolai as the author of the excellent text, The C++ Standard Library , ISBN 0-201-37926-0. David is the author of C++ Solutions: Companion to the C++ Programming Language , Third Edition , ISBN 0-201-30965-3. Both authors are also longtime members of the ANSI/ISO C++ X3J16 Standardization Committee.
Ordering Convention
Most of us write the following
const
or
volatile
declaration thus:
volatile int vInt;
const char *pcChar;
The authors suggest that a better way is:
int volatile vInt; // 1
char const *pcChar; // 2
Here,
const
and
volatile
appear before what they qualify. Since I read declarations from right to left, in //1, I get
vInt
is a volatile int and in //2, I get
pcChar
is a pointer to a
const
char. The real power comes when we use this ordering in
typedef
statements. For example,
typedef int *PINT;
typedef PINT const CPINT;
typedef const PINT PCINT;
Here we can see that the first
typedef
(pointer to int) can be used in the second
typedef
(
const
pointer to
int
). The third
typedef
completely changes the meaning of the type (pointer to
const int
).
typename
Keyword
Historically, we have used the keyword,
class
, in template parameter lists:
template< class T > . . .
However, T could be replaced by things other than a class name, so the use of the new keyword
typename
is preferred.
Reducing Ambiguities
Often, instantiating a template can result in an ambiguity error that is difficult to understand. Here is a simple example:
template< typename T > plot2D(T& x, T& y);
plot2D(3,4); // ok
plot2D(3.14,1); // error - types of arguments differ
The ambiguity occurs because all parameters are supposed to be of same type, but in the second case, it is unclear whether that type should be an
int
or a
double
.
In this simple example (and many others like it), there are 3 ways of disambiguating the statement:
plot2D<int>(3.14,1); // 3
plot2D(static_cast<int>(3.14), 1); // 4
In //3, the template type is forced to be
int
. In //4, casting forces all the argument types to be the same. The third way to eliminate this problem uses more sophisticated templates.
Overloading function template
It is possible to have both template and non-template versions of the same function. Often, non-template versions are called specializations. In such situations, function overloading and its rules are used to resolve any ambiguities. In addition to normal function overloading rules, a few extra rules are needed for templates.
-
In instantiating a template function, no type conversions are done.
-
All things being equal, specialized template functions are preferred over template ones.
-
If instantiating a template function results in a better match, the better match is used. By better match, we mean that things like conversions are not need to make a match.
-
If the empty angle brackets notation is used, non-template functions are ignored in matching argument.
Here are some examples:
int cmp(int const& a, int const& b);
template<typename T> T cmp(T const& a, T const &b);
cmp(1,2); // 5
cmp(4.3, -1.2); // 6
cmp('x','s'); // 7
cmp<>(-3,2); // 8
cmp<int>(4.3,4); // 9
In //5, the non-template function is the best match (Rule 2). In //6, template argument deduction instantiates the
cmp<double>
function (Rule 3). In //7, the instantiated function is
cmp<char>
(Rule 3). In //8, the notation forces use of the template function and results in a
cmp<int>
function being instantiated and used instead of the non-template version of the
cmp
function (Rule 4).
Partial Specialization
When a template class or function has more than one template parameter, one or more may be specified creating a partially specialized template. However, if one or more partial specializations match the same template, an ambiguity occurs. For example,
#include <typeinfo>
#include <stdio.h>
typedef char const CC;
// general template function
template<typename T1,typename T2>
void f(T1 t1, T2 t2) {
CC* s1=typeid(T1).name();
CC* s2 = typeid(T2).name();
printf("f(%s,%s)\n",s1,s2);
}
// partial specialization 1
// both types the same
template<typename T>
void f(T t1, T t2) {
CC* s1 = typeid(T).name();
CC* s2 = typeid(T).name();
printf("f1(%s,%s)\n",s1,s2);
}
// partial specialization 2
// 2nd parameter is non-type
template< typename T>
void f(T t1, int t2) {
CC* s1 = typeid(T).name();
CC* s2 = typeid(int).name();
printf("f(%s,%s)\n",s1,s2);
}
// partial specialization 3
// parameters are all pointers
template< typename T1, typename T2>
void f(T1* t1, T2* t2) {
CC* s1 = typeid(T1*).name();
CC* s2 = typeid(T2*).name();
printf("f(%s,%s)\n",s1,s2);
}
int main(int argc,char* argv[]) {
char const* s1=”one”;
f(1,21.3);
f(1.2,-3);
f('a','z');
f(s1,2);
f(&s1,"two");
return 0;
}
The output from this program is:
f(int,double)
f(double,int)
f(char,char)
f(CC *,int)
f(CC **,char *)
Note that calling f(3,5) is ambiguous because both
f(T,T)
and
f(T,int)
match this call.
Non-type Template Parameters
Both classes and functions can use non-type template parameters. When used, non-type parameters become part of the classes type and functions signature. Thus,
template<typename T, int n>
class C {
T a[n];
// . . .
};
C<char, 10> c10A;
C<char, 5> c5A;
C<int, 10> i10A;
Each of
c10A
,
c5A
, and
i10A
are all distinct types.
An example of a template function using non-type parameter follows:
template<typename T, int n>
T g(T t) {
return t+n;
}
int main() {
printf("g<double,5>(1.3)=%g\n", g<double,5>(1.3));
printf("g<char,4>(‘a’)=%c\n", g<char,4>(‘a’));
}
The output of this short program is:
g<double,5>(1.3)=6.3
g<char,4>('a')=e
Non-type template parameters have restrictions: they must be integral values, enumerations, or instance pointers with external linkage. They can’t be string literals nor global pointers since both have internal linkage.
Keyword
typename
You may have wondered why we needed the new keyword
typename
. Besides being a better choice for template parameters, it is needed to disambiguate certain declarations. E.g.,
template<typename T, int n>
class C {
typename T::X *p;
// . . .
};
Without the keyword
typename
, the declaration for p becomes an expression: the value of
T::X
is multiplied by the value of
p
.
Generally, prefix all declarations using template parameters with
typename
.
this pointer
Normally derived and base classes share the value of
this
. However, lookup rules for template classes are different. Template base class members are not lookup when searching derived template class member functions.
template<typename T>
class B {
void foo();
}
template<typename T>
class D: public B<T> {
void bar() { foo(); }
}
In
bar()
,
foo()
would not be found. To get
B
’s foo, you need to say
this->foo()
. Again, the rule given in the book states that any base class member used in a derived class should be qualified by
this->
or
B<T>::
.
Conclusion
I have not begun to cover many of the interesting aspects of templates in this short article. And I have barely covered the other fun parts of this book. Get it.
Reg Charney
This article was originally published on the ACCU USA website in December 2002 at http://www.accu-usa.org/2002-12.html
Thanks to Reg for allowing us to reprint it.