In issue #007 we talked about the use of inline functions. Suppose that you wish to compute the maximum of two quantities, and you define a C macro for this:
#define max(a, b) ((a) > (b) ? (a) : (b))
This works OK until a case like:
max(x++, y++)
comes along. An inline function:
inline int max(int a, int b) { return a > b ? a : b; }
solves this problem. But what if you want a max() function for a variety of numeric types? You can define a slew of function prototypes:
int max(int, int); long max(long, long); double max(double, double);
and rely on function overloading to sort things out. Or you can define one function that might work in all cases:
long double max(long double, long double);
since nothing can be bigger than a long double, right? This last approach fails because there's no guarantee that, for example, the size of a long is less than the size of a long double, and assigning a long to a long double would in such a case result in loss of precision.
In C++ there is another way to approach this problem, using what are called parameterized types or templates. We can define a function template:
template <class T> inline T max(T a, T b) { return a > b ? a : b; }
The preface "template <class T>" is used to declare a template. T is a template parameter, a type argument to the template. When this template is used:
int a = 37; int b = 47; int i = max(a, b);
the type value of T will be "int", because a and b are integers. If instead I had said:
double a = 37.53; double b = -47.91; double d = max(a, b);
then T would have the type value "double". The process of generating an actual function from a function template is known as "instantiation". Note also that "const T&" may be used instead of "T"; we will be discussing this point in a future issue of the newsletter.
This template will also work on non-numeric types, so long as they have the ">" operator defined. For example:
class A { public: int operator>(const A&); // use "bool" return type // instead, if available }; A a; A b; A c = max(a, b);
Templates are a powerful but complex feature, about which we will have more to say. Languages like C or Java(tm), that do not have templates, typically use macros or rely on using base class pointers and virtual functions to synthesize some of the properties of templates.
Templates in C++ are a more ambitious attempt to support "generic programming" than some previous efforts found in other programming languages. Support for generic programming in C++ is considered by some to be as important a language goal for C++ as is support for object-oriented programming (using base/derived classes and virtual functions; see newsletter issue #008). An example of heavy template use can be found in STL, the Standard Template Library.
In chapter 14 of the draft ANSI/ISO C++ standard is a mention of something called member templates. This feature is new in a way and not yet widely available, but worth mentioning here.
Member templates are simply a generalization of templates such that a template can be a class member. For example:
#include <stdio.h> template <class A, class B> struct Pair { A a; B b; Pair(const A& ax, const B& bx) : a(ax), b(bx) {} template <class T, class U> Pair(const Pair<T,U>& p) : a(p.a), b(p.b) {} }; int main() { Pair<short, float> x(37, 12.34); Pair<long, long double> y(x); printf("%ld %Lg\n", y.a, y.b); return 0; }
This is an adaptation of a class found in the Standard Template Library. Note that an object of class Pair<long, long double> is constructed from an object of class Pair<short, float>. By using a template constructor it is possible to construct a Pair from any other Pair, assuming that conversion from T to A and U to B are supported. Without the availability of template constructors one could only declare constructors with fixed types like "Pair(int)" or else use the template arguments to Pair itself, as in "Pair(A, B)".
In a similar way to function template use, it's possible to have usage like:
template <class T> struct A { template <class U> struct B {/* stuff */}; }; A<double>::B<long> ab;
In this example, the type value of T within the nested template declaration would be "double", while the value of U would be "long".
There are a few restrictions on member templates. A destructor for a class cannot be defined as a function template, nor may a function template member of a class be virtual.
To continue our introduction of C++ templates, we'll be saying a few things about class templates in this issue. Templates are a part of the language still undergoing major changes, and it's tricky to figure out just what to say. But we'll cover some basics that are well accepted and in current usage.
A skeleton for a class template, and definitions of a member function and a static data item, looks like this:
template <class T> class A { void f(); static T x; }; template <class T> void A<T>::f() { // stuff } template <class T> T A<T>::x = 0;
T is a placeholder for a template type argument, and is bound to that argument when the template is instantiated. For example, if I say:
A<double> a;
then the type value of T is "double". The binding of template arguments and the generation of an actual class from a template is a process known as "instantiation". You can view a template as a skeleton or macro or framework. When specific types, such as double, are added to this skeleton, the result is an actual C++ class.
Template arguments may also be constant expressions:
template <int N> struct A { // stuff }; A<-37> a;
This feature is useful in the case where you want to pass a size into the template. For example, a Vector template might accept a type argument that tells what type of elements will be operated on, and a size argument giving the vector length:
template <class T, int N> class Vector { // stuff }; Vector<float, 100> v;
A template argument may have a default specified (this feature is not widely available as yet):
template <class T = int, int N = 100> class Vector { // stuff }; Vector<float, 50> v1; // Vector<float, 50> Vector<char> v2; // Vector<char, 100> Vector<> v3; // Vector<int, 100>
To see how some of these basic ideas fit together, let's actually build a simple Vector template, with set() and get() functions:
template <class T, int N = 100> class Vector { T vec[N]; public: void set(int pos, T val); T get(int pos); }; template <class T, int N> void Vector<T, N>::set(int pos, T val) { if (pos < 0 || pos >= N) ; // give error of some kind vec[pos] = val; } template <class T, int N> T Vector<T, N>::get(int pos) { if (pos < 0 || pos >= N) ; // give error of some kind return vec[pos]; } // driver program int main() { Vector<double, 10> v; int i = 0; double d = 0.0; // set locations in vector for (i = 0; i < 10; i++) v.set(i, double(i * i)); // get location values from vector for (i = 0; i < 10; i++) d = v.get(i); return 0; }
Actual values are stored in a private vector of type T and length N. In a real Vector class we might overload operator[] to provide a natural sort of interface such as an actual vector has.
What would happen if we said something like:
Vector<char, -1000> v;
This is an example of code that is legal until the template is actually instantiated into a class. Because a member like:
char vec[-1000];
is not valid (you can't have arrays of negative or zero size), this usage will be flagged as an error when instantiation is done.
The process of instantiation itself is a bit tricky. If I have 10 translation units (source files), and each uses an instantiated class:
Vector<unsigned long, 250>
where does the code for the instantiated class's member functions go? The template definition itself resides most commonly in a header file, so that it can be accessed everywhere and because template code has some different properties than other source code.
This is an extremely hard problem for a compiler to solve. One solution is to make all template functions inline and duplicate the code for them per translation unit. This results in very fast but potentially bulky code.
Another approach, which works if you have control over the object file format and the linker, is to generate duplicate instantiations per object file and then use the linker to merge them.
Yet another approach is to create auxiliary files or directories ("repositories") that have a memory of what has been instantiated in which object file, and use that state file in conjunction with the compiler and linker to control the instantiation process.
There are also schemes for explicitly forcing instantiation to take place. We'll discuss these in a future issue. The instantiation issue is usually hidden from a programmer, but sometimes becomes visible, for example if the programmer notices that object file sizes seem bloated.
In previous issues we talked about setting up a class template:
template <class T> class A { T x; // stuff };
When this template is used:
A<double> a;
the type "double" gets bound to the formal type parameter T, part of a process known as instantiation. In this example, the instantiated class will have a data member "x" of type double.
What sorts of arguments can be used with templates? Type arguments are allowed, including "void":
template <class T> class A { T* x; }; A<void> a;
The member x will be of type void* in this case. In the earlier example, using void as a type argument would result in an instantiation error, because a data member of a class (or any object for that matter) cannot be of type void.
Usage like:
A<int [37][47]> a1; A< A<void> > a2;
is also valid. Note that in the second example, the second space is required, because ">>" is an operator meaning right shift.
It's also possible to have non-type arguments. Constant expressions can be used:
template <class T, int n> class A { T vec[n]; }; A<float, 100> a;
This is useful in specifying the size of an internal data structure, in this example a vector of float[100]. The size could also be specified via a constructor, but in that case the size would not be known at compile time and therefore dynamic storage would have to be used to allocate the vector.
The address of an external object can be used:
template <char* cp> struct A { /* ... */ }; char c; A<&c> a;
or you can use the address of a function:
template <void (*fp)(int)> struct A { /* ... */ }; void f(int) {} A<f> a;
This latter case might be useful if you want to pass in a pointer of a function to be used internally within the template, for example, to compare elements of a vector.
Some other kinds of constructs are not permitted as arguments:
- a constant expression of floating type - addresses of array elements - addresses of non-static class members - local types - addresses of local objects
Two template classes (a template class is an instantiated class template, that is, a template with specific arguments) refer to the same class if their template names are identical and in the same scope and their arguments have identical values. For example:
template <class T, int n> struct A {}; typedef short* TT; A<short*, 100> a1; A<TT, 25*4> a2;
a1 and a2 are of the same type.
In previous issues we've covered some of the basics of C++ templates. Recall that a template is a class or function skeleton, and is combined with specified type arguments to produce an actual class or function.
Beyond this general mechanism, C++ also allows the programmer to define specialized classes and functions that take the template and implement it for particular types of template arguments.
Suppose, for example, that you have a String template, that supports strings of most anything -- chars, ints, doubles, arbitrary class types, and so on. Now, it's pretty likely that strings of characters will be used heavily, so it might make sense to special case this combination of template and template argument:
template <class T> class String { // stuff }; template <> class String<char> { // stuff }; String<char> x;
The "template <>" notation is fairly new and may not yet be implemented in your local compiler.
This sequence is a bit different from:
template <class T> class String { // stuff }; String<char> x;
In this second case, the default implementation of String is used, whereas in the specialization case, the programmer overrides the default template and provides an implementation of String<char>.
For a function template, a specialization would be defined as:
template <class T> void f(T) {} template <> void f(int) {}
It's also possible to have forward declarations of specializations:
template <class T> class String {}; template <> class String<double>;
or:
template <class T> void f(T) {} template <> void f(unsigned short&);
A specialization must be declared or defined before use, so for example:
template <class T> int f(T) {return 0;} int i = f(12.34); template <> int f(double) {return 37;}
is invalid.
An interesting quirk with function templates concerns the case where you have a C function, mixed in with a function template and specialization:
extern "C" void f(int); template <class T> int f(T) {return 0;} template <> int f(int) { f(37); return 0; }
The f(37) call here is not recursive. Both "void f(int)" and "int f(int)" match the call, and the non-template is preferred in such a case.
It's also possible to have nested specializations, as in:
template <class T> class A { template <class U> class B { template <class V> void f(V) {} }; }; template <> template <> template <> void A<float>::B<double>::f(long double) {}
and you can specialize special member types such as constructors:
struct A { template <class T> A(T) {} }; template <> A::A(short) {}
or static data members:
template <class T> struct A { template <class U> struct B { static int x; }; }; template <> template <> int A<float>::B<long double>::x = 59;
Specializations are a way of special-casing templates for particular argument types, and are useful in a variety of applications. But they can also be abused and make code harder to understand, especially if a reader of the code doesn't pick up on the fact that specializations are present.
In previous issues we've talked about the process of template instantiation, in which template parameters are bound to actual type arguments. For example:
template <class T> class A {}; A<int> a;
At instantiation time, the template formal parameter T is assigned the type value "int".
Instantiation is done based on need -- the generated class A<int> will not be instantiated unless it has first been referenced or otherwise used.
The actual process of instantiation is done in various ways, for example during the link phase of producing an executable program. But it is possible to explicitly force instantiation to occur in a file. For example:
template <class T> class A { T x; void f(); }; template <class T> void A<T>::f() {} template class A<double>;
will force the instantiation of A<double>.
The whole area of instantiation is still in a state of flux, and this feature may not be available with your compiler.
In earlier issues we've seen how a template is something like a class, except that it can be parameterized, that is, type arguments can be supplied to create an actual class from a template through the process of instantiation.
In C++ friends are used to give outside functions and classes access to private members of a class. Friends can also be used with templates, in a similar way. For example:
template <class T> class A { int x; friend void f(); }; void f() { A<double> a; int i = a.x; } int main() { f(); return 0; }
In this example, the function f() gains access to the private members of all instantiated classes that come from the template A, such as A<double>, A<char**>, and so on.
In a similar way, a whole class can be granted access to private members of a template:
template <class T> class A { int x; friend class B; }; class B { public: void f(); }; void B::f() { A<short> a; int i = a.x; } int main() { B b; b.f(); return 0; }
Here, class B is a friend of template A, and so all of B's members can access the private members of A<short>.
India seo freelance web designer India web development ecommerce website developer India
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100