ACCU Home page ACCU Conference Page
Search Contact us ACCU at Flickr ACCU at GitHib ACCU at Google+ ACCU at Facebook ACCU at Linked-in ACCU at Twitter Skip Navigation

pinC++ Templates

Overload Journal #53 - Feb 2003 + Programming Topics   Author: Reg Charney

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.

Overload Journal #53 - Feb 2003 + Programming Topics