Category Archives: C++
Transform Science - Enthought
An Idiot's Guide to C++ Templates - Part 2
Elevating...
In first part of the series, I elaborated following aspects of templates in C++.
- The syntax of C++ templates
- Function Templates and Class Templates
- Templates taking one or more arguments
- Templates taking non-type integral arguments, and taking default arguments.
- The two phase compilation process
- Generic
Pair
andArray
class that can hold any data-type(s).
In this part, I would try to impart more intriguing concepts of templates, its importance and binding with the other features of C++ language, and would also touch upon STL. No, you need not to know STL at all, and I would not dwell deep into STL. I request you to refresh your template understanding by reading first part, before you jump into this one!
- Requirements from the Underlying Type
- Separation of Declaration and Implementation
- Templates and Other Aspects of C++
- Class Templates, Friends
- Class Templates, Operator Overloading
- Class Templates, Inheritance
- Function Pointers and Callbacks
- Templates and Virtual Functions
- Templates and Macros
- Function Overloading
- STL - An Introduction
- Templates and Library Development
- Closure
Requirements from the Underlying Type
There are class templates and function templates, and they work on given type (template argument type). For example, a function template would sum up two values (or entire array), for type T
- But this function template would require operator+
to be present and accessible for the give type (type T
). Similarly, a class would require the target type to have constructor, assignment operator and other set of required operators.
Requirements: Function Templates
Let me start explaining this topic in little simple and elegant manner. Following function would display value of given type on console (using std::cout
):
template<typename T>
void DisplayValue(T tValue)
{
std::cout << tValue;
}
Following set of calls would succeed:
DisplayValue(20); // <int> DisplayValue("This is text"); // <const char*> DisplayValue(20.4 * 3.14); // <double>
Since ostream
(type of cout
) has operator <<
overloaded for all basic types; hence it works for int
, char*
and double
types. There is an implicit call to one of those overloads of operator<<
.
Now, let us define a new structure, having two members in it:
struct Currency { int Dollar; int Cents; };
And have an attempt to use this type against DisplayValue
function template:
Currency c; c.Dollar = 10; c.Cents = 54; DisplayValue(c);
For this call, you will be bombarded with host of errors from your compiler, because the following line fails to compile for the instantiation of DisplayValue
for type Currency
:
std::cout << tValue; // tValue is now of Currency type
Visual C++ will start reporting errors starting with:
GCC compiler would start reporting with:
16: instantiated from here
2: error: no match for 'operator<<' in 'std::cout << tValue'
Errors do differ, but they mean the same thing: None of the overloads of overloaded operator ostream::operator <<
can be called for Currency
. Both of the compilers do report this simple error in at least 100 lines! The error is not entirely because of ostream
, nor because of Currency
, and neither because of templates - but due to assortment of all this into one. At this moment, you have different options available:
- Don't call
DisplayValue
for typeCurrency
, and write another functionDisplayCurrencyValue
instead. This is what most programmers would do, after finding their inabilty to solve the problem with originalDisplayValue
withCurrency
type. Doing this defeats the whole purpose and power of templates in C++. Don't do it! - Modify
ostream
class, and add new member (i.e.operator<<
) that takesCurrency
type. But you don't have liberty to do so, sinceostream
is in one of the C++ standard header. However, with global function, which would takeostream
andCurrency
types, you can do it. - Modify your own class,
Currency
, so thatcout<<tValue
would succeed.
In short, you need to facilitate either of following:
ostream::operator<<(Currency value);
(Simplified syntax)ostream::operator<<(std::string value);
ostream::operator<<(double value);
First version is very much possible, but syntax is slightly complicated. The definition of custom function would take ostream
as well as Currency
types, so that cout<<currency_object;
would work.
Second version demands understanding of std::string
, and would be slower and complex.
Third version fits the requirement at this moment, and is simplest of other two. It means that Currency
be converted to double
, whenever demanded, and the converted data will be passed to cout
call.
And here is the solution:
struct Currency { int Dollar; int Cents; operator double() { return Dollar + (double)Cents/100; } };
Notice that entire result would come out into a double
value. So, for Currency{12, 72}
object, this overloaded operator (function operator double()
) would return 12.72.
Compiler will now be happy, since there is a possible conversion from Currency
to one of the types that ostream::operator<<
takes.
And therefore, the following call, for type Currency
:
std::cout << tValue;
would be expanded as:
std::cout << tValue.operator double(); // std::cout << (double)tValue;
And hence the following call would work:
Currency c; DisplayValue(c);
You see that a simple function call has invoked multiple operations with the help of compiler:
- Instantiating
DisplayValue
for typeCurrency
. - Calling the copy-constructor for class
Currency
,T
is passed by value. - In an attempt to find best match for
cout<<operator
call, the conversion operatorCurrency::
operator double
is invoked.
Just add your own copy-constructor code for Currency
class, and do step-in debugging and see how a simple call is invoking multiple operations!
Alright! Now, let's revisit PrintTwice
function template from previous part of this article-series:
template<typename TYPE> void PrintTwice(TYPE data) { cout<<"Twice: " << data * 2 << endl; }
The important part is marked in bold. When you call it as:
PrintTwice(c); // c is of Currency type
The expression data * 2
in PrintTwice
will work, since Currency
type facilitates this possibility. The cout
statement would be rendered, by the compiler, as:
cout<<"Twice: " << data.operator double() * 2 << endl;
When you remove the overloaded operator double()
from class Currency
, compiler would complain that it doesn't have operator*
, or any possibility where this expression can be evaluated. If you change the class (struct) Currency
as:
struct Currency { int Dollar; int Cents; /*REMOVED : operator double();*/ double operator*(int nMultiplier) { return (Dollar+(double)Cents/100) * nMultiplier; } };
Compiler will be happy. But doing this will cause DisplayValue
to fail for Currency
instantiation. The reasons is simple: cout<<tValue
won't be valid then.
What if you provide both double
conversion as well as multiplication operator? Would the call to PrintTwice
fail for Currency
type? A possible answer would be yes, that the compilation would fail, since compiler would have ambiguity at following the call:
cout<<"Twice: " << data * 2 << endl; // Ambiguity: What to call - conversion operator or operator* ?
But no, compilation will not fail, and it would make a call to operator*
. Any other possible conversion routines may be called, if compiler doesn't find best candidate. Most of the stuff explained here comes from C++ rulebook, and not explicitly under template umbrella. I explained it for better understanding.
It is generally advised that class shouldn't expose conversion operators too much, instead they should provide relevant functions for them. An example is string::c_str()
, which returns C-style string pointer; but another implementations (like CString
) would provide necessary conversions implicitly by providing conversion-operators. But for most cases it is recommended that classes should not expose unnecessary operators just to make it work with any code. It should provide only reasonable implicit conversions.
Same thing goes with templates and underlying types, the underlying type may provide conversion operator (like conversion to int
or string
), but shouldn't provide conversions in excess. Therefore, for Currency
, only double (and/or to string) conversion would suffice - there is no need to give (binary) operator*
overloaded.
Moving on. The following function template:
template<typename T>
T Add(T n1, T n2)
{
return n1 + n2;
}
would require T
to have operator+
which takes argument of same type, and returns same type. In Currency
class, you would implement it as:
Currency operator+(Currency); // No consts, for simplification
Interestingly, if you do not implement operator+
in Currency
, and keep the double
conversion operator present in class; and call function Add
as:
Currency c1, c2, c3; c3 = Add(c1,c2);
You would get somewhat weird error:
Reason is two fold:
n1 + n2
is causingoperator double()
be called for both objects passed (implicit conversion). Therefore,n1+n2
becomes a simpledouble+double
expression, resulting intodouble
as final value.- Since the final value is of type
double
, and the same is to be returned from functionAdd
; compiler would try to convert thisdouble
toCurrency
. Since there is no constructor available that takesdouble
, hence the error.
For this situation, Currency
may provide a constructor (a conversion constructor) that takes double
. It makes sense, since Currency
now provides both TO and FROM double
conversions. Providing operator+
just for the sake of Add
function template doesn't make much of sense.
But, I am not at all against on providing many or all required implicit conversions or overloaded operators. In reality, you should provide all required stuff from the underlying type, if the template class/function is designed such way. A class template performing complex number calculation, for example, should be provided with all mathematical operators from the underlying type.
I see most of you are just reading this article, without attempting to do anything. So, here is exercise for you. Find out the type requirements, and fulfill those requirements for Currency
class for the following function template (taken as-is from first part):
template<typename T> double GetAverage(T tArray[], int nElements) { T tSum = T(); // tSum = 0 for (int nIndex = 0; nIndex < nElements; ++nIndex) { tSum += tArray[nIndex]; } // Whatever type of T is, convert to double return double(tSum) / nElements; }
Hey, don't be lazy... Come on! Fulfill the requirements of GetAverage
for Currency
type. Templates are not theoretical, but very much practical! Don't read ahead, until you understand every bit of text till here.
Requirements: Class Templates
As you should know, class templates would be more prevalent than function templates. There are more class templates in STL, Boost and other standard libraries than function templates. Believe me, there would be more class templates who would craft, than number of function templates you would code.
Class templates would ask for more requirements from the underlying type. Though, it would be mostly dependent on what you demand from the class template itself. For example, if you do not call min
/max
equivalent method from class- template instantiation, the underlying type need not to provide relevant relational operators.
Most class templates would demand following from the underlying type:
- Default constructor
- Copy constructor
- Assignment operator
Optionally, depending on the class template itself (i.e. the purpose of class template), it may also ask for:
- Destructor
- Move constructor and Move assignment operator
(On top of R- Value references)
Only for the sake of simplicity, I would not cover the second group of basic requirements, at least not in this article. Let me start off with the first group of basic-requirements understanding from underlying type, followed by explication of more type requirements.
Note that all required-methods, when provided by the underlying type, must be accessible (i.e. public) from the class template. For instance, a protected assignment operator from Currency
class won't help for some collection class that demands it. The assignment of one collection to another collection (both of Currency
) will not compile due to protected nature of Currency
.
Let's revisit set of classes I used in first part, for this sub-section. Following is a class template Item
, for holding any type:
template<typename T> class Item { T Data; public: Item() : Data( T() ) {} void SetData(T nValue) { Data = nValue; } T GetData() const { return Data; } void PrintData() { cout << Data; } };
The mandatory requirement, as soon as you instantiate Item
for particular type, is having default constructor in type T
. Therefore, when you do:
Item<int> IntItem;
The type int
must have default constructor (i.e. int::int()
), since this class' template constructor would be calling default constructor for underlying type:
Item() : Data( T() ) {}
We all know that int
type has its default constructor, which initializes the variable with zero. When you instantiate it for another type, and that type is not having default constructor, the compiler would be upset. To understand this, let's craft a new class:
class Point { int X, Y; public: // No default constructor Point(int x, int y); };
As you know, following call would fail to compile (commented code would compile).
Point pt; // ERROR: No default constructor available // Point pt(12,40);
Similarly, following instantiation would also fail:
Item<Point> PointItem;
When you use Point
directly, without using non-default constructor, the compiler will report it, and you would able to correct it - since the source (line) would be reported correctly.
But, when you use it against Item
class template, the compiler would the report the error near the Item
implementation. For PointItem
example mentioned above, Visual C++ reports it at following line:
Item () : Data( T() ) {}
And the error is reported as:
: while compiling class template member function 'Item<T>::Item(void)'
with
[
T=Point
]
: see reference to class template instantiation 'Item<T>' being compiled
with
[
T=Point
]
Which is very small error message, as far as C++ template error reporting is concerned. Most often, the first error would tell the whole story, and the last error reference ("see reference to class...") shows the actual cause of the error. Depending on your compiler, the error may be easy to understand, or may be quite elusive to get to the actual cause of the error. The quickness and ability of finding and fixing the bug/error would depend on your experience with template programming.
So, to use Point
class, as a template type parameter for Item
, you must provide the default constructor in it. Yes, a single constructor taking zero, one or two parameter would also work:
Point(int x = 0, int y = 0 );
But, the constructor must be public (or accessible, by other means).
Alright. Let's see if SetData
and GetData
methods would work for type Point
. Yes, both of them would work since Point
has a (compiler provided) copy constructor and assignment operator.
If you implement assignment operator in class Point
(the underlying type for Item
), that particular implementation will be called by Item
(yes, the compiler will generate the relevant code). The reason is simple: Item::SetData
is assigning the value:
void SetData(T nValue) // Point nValue { Data = nValue; // Calling assignement operator. }
If you put the implementation of assignment operator of Point
class in private/protected area:
class Point { ... private: void operator=(const Point&); };
And make a call to Item::SetData
:
Point pt1(12,40), pt2(120,400); Item<Point> PointItem; PointItem.SetData(pt1);
It would cause a compiler error, starting with something like:
The compiler would point you the location of error (inside SetData
), as well as the actual source of error (call from main
). Actual error messages, and sequence of messages shown would depend on compiler, but most modern compiler will attempt to give detailed error so that you can find the actual source of error.
This example shows that special method of underlying type T
are called, depending on how/what call is being made. Remember that special members (constructors, assignment operators etc) may be called implicitly - while returning from function, passing value to function, using expressions and so on. It is therefore recommended that underlying type (type T
) should have these special methods implemented in accessible region of class.
This example also demonstrates how different classes and functions are involved in one function call. In this case, only Point
class, the class template Item
and function main
are involved.
Similarly, when you make a call to Item<Point>::GetItem
method, the copy constructor would be called (of Point
class). The reason is simple - GetItem
is returning a copy of data stored. A call to GetItem
may not always call copy-constructor, as RVO/NRVO, move-semantics etc may come into picture. But you should always make the copy-constructor of underlying type accessible.
The method Item<>::
PrintData
would not succeed for Point
type, but would succeed for Currency
, as underlying type for Item
. Class Point
doesn't have conversion, or any possible call, to the cout<<
call. Please do yourself a favor - make Point
class cout
-able!
Separation of Declaration and Implementation
Till now I have shown the entire implementation of template-code in one source-file. Treat the source-file as one header file, or being implemented in same file containing main
function. As any C/C++ programmer would know, we put declarations (or say, interfaces) in header file, and the respective implementation in one or more source files. The header file would be included by both - respective implementation file, and by one or more client of that interface.
At the compilation unit level, the given implementation file would get compiled, and an object file for the same would be generated. When linking (i.e. while generating the final executable/DLL/SO), the linker would gather all those generated object files and would produce final binary image. All good, all fine, unless linker gets upset by missing symbols or duplicate symbols being defined.
Come to templates, it is not exactly the same way. For better understanding let me first throw you some code:
Sample.H
template<typename T>
void DisplayValue(T tValue);
Sample.CPP
template<typename T>
void DisplayValue(T tValue)
{
std::cout << tValue;
}
Main.cpp
#include "Sample.H" int main() { DisplayValue(20); DisplayValue(3.14); }
Depending on the compiler and IDE you use, you would put both CPP files for the build. Surprisingly, you will encounter linker errors like:
unresolved external symbol "void __cdecl DisplayValue<int>(int)" (??$DisplayValue@H@@YAXH@Z)
(.text+0xfc): undefined reference to `void DisplayValue<double>(double)'
When you closely look at the errors, you would find out that linker could not find implementation for following routines:
void DisplayValue<int> (int); void DisplayValue<double> (double);
Despite the fact that you have provided implementation of template function DisplayValue
via source file Sample.CPP.
Well, here is the secret. You know that template function gets instantiated only when you make a call to it, with particular data-type(s). The compiler compiles Sample.CPP
file separately having definition of DisplayValue
. The compilation of Sample.CPP is done in separate translation unit. Compilation of Main.cpp is done in another translation unit. These translation units produce two object files (say Sample.obj and Main.obj).
When the compiler works on Sample.CPP, it does not find any references/calls to DisplayValue
, and it doesn't instantiate DisplayValue
for any type. Reason is simple, as explained earlier - On-Demand-Compilation. Since the translation-unit for Sample.CPP doesn't demand any instantiation (for any data-type), it doesn't do second-phase compilation for function template. No object code is generated for DisplayValue<>
.
In another translation-unit, Main.CPP gets compiled and object code gets generated. While compiling this unit, the compiler sees a valid interface declaration for DisplayValue<>
, and performs its job without any issue. Since, we have called DisplayValue
with two different types, compiler intelligently produces following declarations by itself:
void DisplayValue<int>(int tValue); void DisplayValue<double>(double tValue);
And as per its normal behavior, the compiler assumes definitions of these symbols in some other translation-unit (i.e. object code), and delegates further responsibility to the linker. This way, Sample.obj and Main.obj files get generated, but none of them do contain implementation of DisplayValue
- and hence the linker produces set of errors.
What's the solution for this?
The simplest solution, which works for all modern compiler is using the Inclusion Model. Another model, not supported by most major compiler vendors is Separation Model.
Till now, whenever I explained about template stuff with code written in same file, I used inclusion model. In simple terms, you put all template related code in one file (generally a header file). The client would just include the given header file, and entire code would be compiled in one translation unit. Yet, it would follow on-demand-compilation process.
For the example given above, Sample.H would contain the definition (implementation) of DisplayValue
:
Sample.H
// BOTH: Interface and Implementation template<typename T> void DisplayValue(T tValue) { std::cout << tValue; }
Main.CPP would just include this header file. The compiler will be happy, and the linker will also be happy. If you prefer, you may put all declarations, followed by definitions of all functions later in the same file. For example:
template<typename T> void DisplayValue(T tValue); template<typename T> void DisplayValue(T tValue) { std::cout << tValue; }
It has following advantages:
- Logical grouping of all declarations, and all implementations.
- No compiler errors if a template function
A
needs to useB
, andB
also needs to useA
. You would have already declared the prototypes for the other function. - Non-inlining of class methods. Till now, I have elaborated entire template class within the class' declaration body. Separating out the method implementation, is discussed later.
Since you can logically divide interface and implementation, you can also figuratively divide them into Dot-H and Dot-CPP files:
template<typename T> void DisplayValue(T tValue); #include "Sample.CPP"
Sample.H is giving prototype for DisplayValue
, and at the end of file, it is including Sample.CPP. Don't worry, it is perfectly valid C++ and would work with your compiler. Note that, your project/build now must not add Sample.CPP for compilation process.
The client (Main.CPP) will include the header, which is adding the code of Sample.CPP into it. In this case, just one translation unit (for Main.cpp) will do the trick.
Separating Class Implementation
The section demands more attention from the readers. Implementing a method outside a class requires complete type-specification. For example, let's implement Item::SetData
outside the class definition.
template<typename T> class Item { ... void SetData(T data); }; template<typename T> void Item<T>::SetData(T data) { Data = data; }
Note the expression Item<T>
while mentioning the class for which method is being defined. Implementing SetData
method with Item::SetData
will not work, since Item
is not a simple class, but a class template. The symbol Item
is not a type, but some instantiation of Item<>
is a type, and therefore the expression Item<T>
.
For instance, when you instantiate Item
with type short
, and use SetData
method for it,
Item<short> si;
si.SetData(20);
the compiler would generate source-code like this:
void Item<short>::SetData(short data) { Data = data; }
Here, the formed class name is Item<short>
and SetData
is being defined for this class type.
Let's implement other methods outside the class body:
template<typename T> T Item<T>::GetData() const { return Data; } template<typename T> void Item<T>::PrintData() { cout << Data; }
Clearly notice that template<typename T>
is required in all cases, and Item<T>
is also required. When implementing GetData
, the return type is T
itself (as it should be). In PrintData
implementation, though T
is not used, Item<T>
specification is needed anyway.
And finally, here is the constructor implemented outside class:
template<typename T> Item<T>::Item() /*: Data( T() ) */ { }
Here, the symbol Item<T>
is the class, and Item()
is the method of this class (i.e. the constructor). We need not to (or cannot, depending on compiler) use Item<T>::Item<T>()
for the same. Constructor (and destructor) are special member methods of class, and not class types, and therefore they shouldn't be used as type, in this context.
Only for simplicity I commented default initialization of Data
with the default constructor-call for type T
. You should uncomment the commented part, and understand the meaning.
If the template class has one or more default types/non-types as template parameters, we just need to specify them while declaring the class:
template<typename T = int> // Default to int class Item { ... void SetData(T data); }; void Item<T>::SetData() { }
We cannot/need-not specify the default template parameters, at the implementation stage:
void Item<T = int>::SetData() {} // ERROR
That would simply be an error. The rule and reasoning is very much similar to a C++ function taking default argument(s). We only specify default parameter(s) when declaring the interface of a function, and not when (separately) implementing the function. Eg:
void Allocate(int nBytes = 1024); void Allocate(int nByte /* = 1024* / ) // Error, if uncommented. { }
Implementing Method Templates Outside Class
For this, first consider a simple example by a code snippet:
Item<int> IntItem;
Item<short> ShortItem;
IntItem.SetData(4096);
ShortItem = IntItem;
The important line of discussion is the one which is marked bold. It tries to assign Item<int>
object to Item<short>
instance, which is not possible, since these two are different types. Of course, we could have used SetData
on one and GetData
on another object. But what if we needed the assignment to work?
For this, you can implement a custom assignment operator, which would itself be on top of templates. That would be classified as method template, and it was already covered in first part. This discussion is only for outside-class implementation. Anyway, here is the in-class implementation:
template<typename U> void operator = (U other) { Data = other.GetData(); }
Where U
is the type of other class (another instantiation of Item
). I didn't use const
and reference specification for other
argument, only for simplicity. When ShortItem = IntItem
takes place, following code is generated:
void operator = (Item<int> other) { Data = other.GetData(); }
Note that other.GetData()
returns int
, and not short
, since source object other
is of type Item<int>
. If you call this assignment operator with non-convertible types (such as int*
to int
), it would cause compiler error as these two types are not implicitly convertible. One should not use any type of typecasting within template code, for these kind of conversions. Let the compiler report error to the client of your template.
One more interesting thing worth mentioning here. If you code above' assignment operator like this:
template<typename U> void operator = (U other) { Data = other.Data; }
It simply won't compile - the compiler would complain that Data
is private! You would wonder why?
Reason is quite simple: this class (Item<short>
), and the other class (Item<int>
) are actually two different classes and have no connection between them. By standard C++ rule, only same class can access private data of current class. Since Item<int>
is another class, it doesn't give private access to Item<short>
class, and hence the error! That's the reason I had to use GetData
method instead!
Anyway, here is how we implement a method template outside the class declaration.
template<typename T> template<typename U> void Item<T>::operator=( U other ) { Data = other.GetData(); }
Note that we need to use template specification with template
keyword two times - one for class template, and one for method template. Following will not work:
template<typename T, class U> void Item<T>::operator=( U other )
Reason is simple - class template Item
does not take two template-arguments; it takes only one. Or, if we take it other way around - method template (i.e. assignment operator) doesn't take two template arguments. Class and methods are two separate template-entities, and need to be classified individually.
You should also notice that <<code>class T
> comes first, followed by <
class U>
. Studying it by the left-to-right parsing logic of the C++ language, we see that class Item
comes first then the method. You may treat the definition as (see tabs):
template<typename T> template<typename U> void Item<T>::operator=( U other ) { }
The separation of two-template specification is definitely not dependent on the original parameter names used in class and method declarations. By this, I mean U
and T
can exchange positions, and it would compile fine. You can also name the way you like - other than T
or U
.
template<typename U> template<typename T> void Item<U>::operator=( T other ) { }
The order of arguments must match, however. But, as you can understand, using the same name is recommended for readability.
Read and understand enough? Well, then it is time to test yourself by writing some template code! I just need the following to work:
const int Size = 10; Item<long> Values[Size]; for(int nItem = 0; nItem < Size; ++nItem) Values[nItem].SetData(nItem * 40); Item<float> FloatItem; FloatItem.SetAverageFrom(Values, Size);
The method-template SetAverageFrom
would calculate the average from the Item<>
array being passed. Yes, the argument (Values
) may be of any underlying type Item
-array. Implement it outside class body! Irrespective of who you are - A super-genuis in C++ templates or if you think this task as Rocket-Science tough, you must do it - Why fool yourself?
Additionally, what would you do if Values
is an array of underlying type Currency
?
Most of the template implementations would be using inclusion model only, that too only in one header file, and all inline code! STL, for example, uses header-only, inline implementation technique. Few libraries are using include-other-stuff technique - but they require only header to be included by client, and they operate on top of inclusion model only.
For most template related code, inlining doesn't harm for few reasons.
One, the template code (class, function, entire-library) is generally short and concise, like implementing a class template less
which calls operator <
on the underlying type. Or a collection class which puts and reads the data into collection, without doing too much of laborious work. Most classes would do small and only-required tasks like calling a function-pointer/functor, performing string related stuff; and would not do intensive calculations, database or file read, sending packet to network, preparing buffer to download and other intensive work.
Two, inlining is just a request from the programmer to the compiler and compiler would inline/not-inline the code on its own discretion. It all depends on code complexity, how often it (method) gets called, other possible optimizations around it etc. Linker and profile guided optimization (PGO) also play important role in code optimization, inlining etc. Therefore, putting entire code within class-definition will not do any harm.
Three, not all of the code gets compiled - only the one that gets instantiated would get compiled, and this reasoning get more importance because of previous two points mentioned. So, don't worry about code inlining!
When set of class templates along with few helper function templates, the code is just like an arithmetic expression for the compiler. For example, you would use std::count_if
algorithm on a vector<int>
, passing a functor, which would call some comparison operator. All this, when coupled in single statement, may look complicated and seems processor-intensive. But it is not! The entire expression, even involving different class templates and function templates, is like a simple expression to the compiler - specially in a Release build.
Other model, the Separation Model, works on top of export
keyword, which most compilers don't still support. Neither GCC, nor Visual C++ compiler support this keyword - both compiler would however say this is reserved keyword for future, rather than just throwing non-relevant error.
One concept that logically fits this modeling umbrella is Explicit Instantiation. I am deferring this concept and I would be elaborating it later. One important thing - Explicit Instantiation and Explicit Specialization are two different facets! Both will be discussed later.
Templates and Other Aspects of C++
Gradually, as you would gain firm understanding about the templates, the power of templates in C++ and be passionate about templates, you would get clear picture, that using templates you can craft your own language subset. You can program the C++ language so that it performs some tasks the way you like. You can use and abuse the language itself, and ask compiler to generate source- code for you!
Fortunately, or unfortunately, this section is not about how to abuse the language and make compiler do labor-work for you. This section tells how other concepts like inheritance, polymorphism, operator overloading, RTTI etc. are coupled with templates.
Class Templates, Friends
Any veteran programmer would know the real importance of friend
keyword. Any newbie or by-the-books mortal may detest friend
keyword, saying that it breaks encapsulation, and the third category would say "depends". Whatever your perspective may be, but I believe that friend
keyword is useful, if judiciously used wherever required. A custom allocator for various classes; a class to maintain the relation between two different classes; or an inner class of a class are good candidates of being friend
s.
Let me first give you an example, where the friend
keyword along with template is almost indispensable. If you remember the template-based assignment operator in Item<>
class, you must also recollect that I had to use GetData
from the other object of another type (another variant of Item<>
). Here is the definition (in-class):
template<typename U> void operator = (U other) { Data = other.GetData(); }
The reason is simple: Item<T>
and Item<U>
would be different types, where T
and U
may be int
and short
, for example. One class cannot access private member of another class. If you implement an assignment operator for regular class, you would directly access the data of other object. What would you do to access data of other class (which is, ironically, the same class!) ?
Since the two specializations of class template belong to same class, can we make them friends? I mean, is it possible to make Item<T>
and Item<U>
friends of each other, where T
and U
are two different data-types (convertible)?
Logically, it is like:
class Item_T { ... friend class Item_U; };
So that Item_U
can access Data
(private data) of class Item_T
! Remember that, in reality, Item_T
andItem_U
would not be just two class-types, but any set of two instantiations on top if class template Item
.
Self-friendship seems logical, but how to achieve that? Following will simply not work:
template<typename t> class Item { ... friend class Item; };
Since Item
is a class template and not a regular class, therefore symbol Item
is invalid in this context. GCC reports following:
error: 'int Item<int />::Data' is private
Amusingly, initially it says it is implicit friend with itself, and later it complains about private-access. Visual C++ compiler is more lenient and silently compiles, and makes them friends. Either way, the code is not compatible. We should use code that is portable and indicates Item
as class template. Since target type is unknown, we cannot replace T
with any particular data-type.
Following should be used:
template <class U> friend class Item; // No template stuff around 'Item'
It forward-declares the class, and implies that Item
is class template. The compiler is now satisfied, without any warnings. And now, following code works without the penalty to call the function:
template<typename U> void operator = (U other) { Data = other.Data; // other (i.e. Item<U> has made 'me' friend. }
Other than this self-friendship notion, the friend
keyword would be useful along with templates in many other situations. Of course, it includes regular course of friendships, like connecting a model and a framework class; or a manager class being declared as friend by other worker classes. But, in case of templates, an inner class of a template-based outer class may have to make outer class a friend. Another case where template-based base class would be declared as friend by derived class.
At present, I do not have more ready and understandable examples to demonstrate friend
keyword usage, that are specific to class templates.
Class Templates, Operator Overloading
Class templates would use operator-overloading idea, more often that a regular class would do. A comparator class would use one or more of relational operators, for instance. A collection class would use index-operator to facilitate get or set operations for element access by index or by key. If you remember class template Array
from previous part, I used index-operator:
template<typename T, int SIZE> class Array { T Elements[SIZE]; ... public: T operator[](int nIndex) { return Elements[nIndex]; } };
For another example, recollect a class template Pair
discussed in previous part. So, for example, if I use this class template as:
int main() { Pair<int,int> IntPair1, IntPair2; IntPair1.first = 10; IntPair1.second = 20; IntPair2.first = 10; IntPair2.second = 40; if(IntPair1 > IntPair2) cout << "Pair1 is big."; }
That simply won't work and requires operator >
to be implemented by class template Pair
:
// This is in-class implementation bool operator > (const Pair<Type1, Type2>& Other) const { return first > Other.first && second > Other.second; }
</span /></span /></span />Though, same thing was already discussed in first part (for operator ==
), I added this word only for relevance with the concept being elaborated.
Other than these regular overloadable operators, like relational operators, arithmetic operators etc, templates also employ other rarely used overloadable operators: Arrow Operator (- >
) and the Pointer Indirection operator (*
). A simple smart-pointer implementation, on top of class template, illustrates the usability.
template<typename Type> class smart_ptr { Type* ptr; public: smart_ptr(Type* arg_ptr = NULL) : ptr(arg_ptr) {} ~smart_ptr() { // if(ptr) // Deleting a null-pointer is safe delete ptr; } };
The class template smart_ptr
would hold up a pointer of any type, and would safely delete the memory allocated, in the destructor. Usage example:
int main() { int* pHeapMem = new int; smart_ptr<int> intptr(pHeapMem); // *intptr = 10; }
I have delegated the responsibility of memory deallocation to the smart_ptr
object (intptr
). When the destructor of intptr
would get called, it would delete the memory allocated. Note that the first line in main function is just for better clarity. The constructor of smart_ptr
may be called as:
smart_ptr<int> intptr(new int);
NOTE: This class (smart_ptr
) is only for illustration purpose, and it is functionally not equivalent to any of standard smart pointer implementations (auto_ptr
, shared_ptr
, weak_ptr
etc.).
Smart pointer would allow any type to be used for safe and sure memory-deallocation. You could also use any UDT:
smart_ptr<Currency> cur_ptr(new Currency);
After end of current block (i.e. - {}
), the destructor of smart_ptr<>
would get called, and would invoke delete
operator on it. Since the type is known at compile time (instantiation is compile-time!), the destructor of correct type would be invoked. If you put destructor of Currency
, that would be called as soon as cur_ptr
ceases to exist.
Coming back on track; how would you facilitate following:
smart_ptr<int> intptr(new int); *intptr = 10;
For sure, you would implement pointer indirection (unary) operator:
Type& operator*()
{
return *ptr;
}
Distinctly understand that the above definition is non-const implementation, and that is the reason it returns reference of object (*ptr
, not ptr
) being held by class instance. Only because of the same, assignment of value 10
is allowed.
Had it been implemented as const
method, it would not allow assignment to succeed. It would generally return a non-referenced object, or const-reference of the object being held:
// const Type& operator*() const Type operator*() const { return *ptr; }
Following code snippet shows its usage:
int main() { smart_ptr<int> intptr(new int); *intptr = 10; // Non-const show_ptr(intptr); } // Assume it implemented ABOVE main void show_ptr(const smart_ptr<int>& intptr) { cout << "Value is now:" << *intptr; // Const }
You may like to return co<code>nst Type&
for saving few bytes of program stack, from a const
function. But, in general, class templates do return value types instead. It keeps the design simple, avoids any possible bug from creeping in if underlying type has const/non-const blunder in implementation. It also avoids any unnecessary reference creation even from small types (like int
or Currency
), which would turn to be more heavier than value type returns.
Quite interestingly, you can templatize the show_ptr
function itself, so that it can display value of any underlying type under the smart_ptr
object. There is a lot more to explicate about template functions/classes that itself take another template, but needs a separate discussion area for the same. Keeping simple and to-the-point discussion, here is modified show_ptr
:
template<typename T> void show_ptr(const smart_ptr<T>& ptr) { cout << "Value is now:" << *ptr; // Const }
For Currency
object, the function will call Currency::operator double
, so that cout
will work. Are you awake, or you need to refresh stuff about cout
and Currency
? If in confusion, please read that stuff again.
Moving on, lets see what happens when you try to do the following.
smart_ptr<Currency> cur_ptr(new Currency); cur_ptr->Cents = 10; show_ptr(cur_ptr);
The bold line, logically correct, but will fail. Reason is simple - cur_ptr
is not a pointer, but a normal variable. Arrow operator can only be called if expression on left is a pointer to structure (or class). But, as you see, you are using smart_ptr
as a pointer-wrapper around Currency
type. Therefore, this should aesthetically work. Essentially, it means, you need to overload arrow operator in class smart_<code>ptr
!
Type* operator->() { return ptr; } const Type* operator->() const { return ptr; }
Since I do respect your comfort level with the C++ language, I don't find it necessary to explain about these two different overloads implemented. After the implementation of this operator, cur<code>_ptr->Cents
assignment will work!
In general, operator ->
will return a pointer only (of some struct/class). But that's not absolutely necessary - operator->
may also return reference/value of particular class type. It is not really useful, deep down concept and is rarely implement that way, I don't find it worth discussing.
Do apprehend that overloaded operator->
in smart_ptr
will not cause any compile-time error for smart_ptr<int>
, just because int
cannot have arrow-operator applied to it. The reason is simple, you will not call call this operator on smart_ptr<int>
object, and hence compiler will not (attempt to) compile smart_ptr<>::operator->()
for it!
By now, you must have realized importance of operator overloading in C++ and in template arena. Under template domain, there is much more around operators, and it really helps template based development, compiler support, early binding etc.
Class Templates, Inheritance
Before discussing the usability of inheritance along with template-based classes, I would emphasize different modes of inheritance involved. No, it is not about multiple, multilevel, virtual or hybrid inheritance, or base class having virtual functions. The modes are just around single inheritance:
- Class template inheriting Regular class
- Regular class inheriting Class Template
- Class Template inheriting another Class Template
In template-based class designs, other than single inheritance, multiple inheritance would be more frequent than multilevel, hybrid or virtual inheritance. Let me first start off with single inheritance.
You know that class-template is a template for a class, which will be instantiated depending on the types(s) and other argument it takes. The instantiation would produce a template-class, or more distinctly, specialization of that class. The process is known as instantiation, and the outcome is known as specialization.
When inheriting, what would you inherit - a class-template (Item<T>
), or the specialization (Item<int>
) ?
These two different models appear same, but are entirely different. Let me give you an example.
class ItemExt : public Item<int> { }
Here you see that normal class ItemExt
is inheriting from a specialization (Item<int>
), and is not facilitating any other instantiation of Item
. What does it mean? You might ask.
First consider this : The empty class ItemExt
, in itself, can be classified as:
typedef Item<int> ItemExt;
Either way (typedef or inheritance), when you use ItemExt
, you don't need to (or say, you cannot) specify the type:
ItemExt int_item;
int_item
is nothing but a derived-class object of type Item<int>
. This mean, you cannot create object of other underlying type using the derived class ItemExt
. The instance of ItemExt
will always be Item<int>,
even if you add new methods/members to derived class. The new class may provide other features like printing the value, or comparing with other types etc, but class doesn't allow flexibility of templates. By this, I mean, you cannot do:
ItemExt<bool> bool_item;
Since ItemExt
is not a class template, but a regular class.
If you are looking for this kind of inheritance, you can do so - it all depends on your requirements, and design perspective.
Another type of inheritance would be template-inheritance, where you would inherit the class template itself and pass the template-parameters to it. Example first:
template<typename T> class SmartItem : public Item<T> { };
Class SmartItem
is another class template which is inheriting from Item
template. You would instantiate SmartItem<>
with some type, and same type would be passed to class template Item
. And all this would happen at compile time. If you instantiate SmartItem
with char
type, Item<char>
and SmartItem<char>
would be instantiated!
As an another example of template-inheritance, let inherit from class template Array
:
template<size_t SIZE> class IntArray : public Array<int, SIZE> { }; int main() { IntArray<20> Arr; Arr[0] = 10; }
Note that I have used int
as first template argument, and SIZE
as second template argument to base class Array
. The argument SIZE
is only argument for IntArray
, and is second argument for base class Array.
This is allowed, interesting feature and facilitates automatic code generation with the help of compiler. However, IntArray
would always be array of int
s, but the programmer may specify the size of array.
Similarly, you may inherit Array
this way also:
template<typename T> class Array64 : public Array<T, 64> { }; int main() { Array64<float> Floats; Floats[2] = 98.4f; }
Though, in the examples given above, the derived class itself do not do anything extra, inheritance is very-much required. If you think following template-based typedef
will do the same, you are wrong!
template<typename T> typedef Array<T, 64> Array64; typedef<size_t SIZE> typedef Array<int, SIZE> IntArray;
Template based typedef
s are not allowed. Although I do not see any reason why compilers cannot provide such feature. On top of templates typedef
s can behave different depending on the context (i.e. based on template parameters). But template-based typedefs at global level are not allowed.
Though, not specific to the template-inheritance discussion, you may achieve typedef without using inheritance also. But in that case too, you need to define a new class.
template<size_t SIZE> struct IntArrayWrapper { typedef Array<int, SIZE> IntArray; };
Usage is slightly different:
IntArrayWrapper<40>::IntArray Arr; Arr[0] = 10;
The choice entirely depends on the requirement, flexibility, readability, some coding-standards and by personal choice. The second version is quite cumbersome, in my opinion.
But, if inheritance is desired, and you are going to provide extra features on top of base-template, and/or there is "is-a"relationship between base and derived classes, you should use template-inheritance model.
Note that, in almost all cases, template based classes wouldn't have virtual functions; therefore, there is no added penalty on using inheritance. Inheritance is just a data-type modeling, and in simple cases, derived class would also be POD (Plain Old Data). Virtual functions, with templates, will be described later.
By now, I have elaborate two models of inheritance:
- Regular class inheriting class template
- Class template inheriting another class template
I also explicated the difference between template-inheritance (where you pass the template argument(s) to base), and instantiation-inheritance where you inherit from very specific type of template instantiation (called specialization). Note that both IntArray
and Array64
would be classified as template-inheritance, since at least one template-argument is keeping the specialization to happen, and would happen only when derived type is instantiated with specific arguments.
Note that only ItemExt
is an example of 'Regular class inheriting class template'. All other examples given are 'class -template inheriting class-template'.
Now the third type. Can a class-template inherit a regular class?
Who said No? Why not!
I don't find or craft any example where base class would be (absolutely) basic, without any stain of template. It is actually unreasonable and would be a bad design to represent "is-a" relationship from a non-template base class. Initially, I thought of giving an example where base class would be singly-linked list, and derived class, based on template would be somewhat smarter (say doubly) linked list.
The bad example:
class SinglyLinkedList { // Assume this class implements singly linked list // But uses void* mechanism, where sizeof data is // specified in constructor. }; template<class T> class SmartLinkedList : public SinglyLinkedList { };
Now, you can say a SmartLinkedList<>
object is-a SinglyLinkedList
, which defeats the whole purpose of templates. A template-based class must not depend on non-template class. Templates are abstraction around some data-type for some algorithm, programming-model, a data-structure.
In fact, templates do avoid inheritance feature of OOP, altogether. It represents most of the abstractions by a single class. By this I do not mean templates would not use inheritance. In fact, many features around templates rely on inheritance feature of C++ - but it would not use inheritance-of-features, as in classical sense of Object-Oriented-Programming.
Class Templates would use inheritance of rules, inheritance of modeling, inheritance of designs and so on. One example would be make a base class, having copy-constructor and assignment-operator private, without having any data-members in it. Now, you can inherit this Rule class, and make all desired classes non-copyable!
Let me finish all major aspects of C++ along with templates, then I would show you some truly intriguing techniques using templates!
Function Pointers and Callbacks
As you do know function-pointer is one of the mechanism in C/C++ language to achieve dynamic-polymorphism. Not necessarily, but generally coupled with callback-feature - you set particular user-defined function as a callback, that would be called later. The actual function to be called is determined at runtime, and hence late-binding of specific callable function occurs.
To understand, let us consider simple code-snippet.
typedef void (*DisplayFuncPtr)(int); void RenderValues(int nStart, int nEnd, DisplayFuncPtr func) { for(;nStart<=nEnd; ++nStart) func(nStart); // Display using the desired display-function } void DisplayCout(int nNumber) { cout << nNumber << " "; } void DisplayPrintf(int nNumber) { printf("%d ", nNumber); } int main() { RenderValues(1,40, DisplayCout); // Address-Of is optional RenderValues(1,20, &DisplayPrintf); return 0; }
In this code, DisplayFuncPtr
gives the prototype of the desired function, and is only for better readability. Function RenderValues
will display the numbers using the given function. I called this function with different callbacks (DisplayCout
and DisplayPrintf
) from main function. Late-binding occurs at the following statement.
func(nStart);
Here func
may point to either of the two Display-functions (or any other UDF). This type of dynamic-binding has several issues:
- The prototype of callback function must exactly match. If you change
void DisplayCout(int)
tovoid DisplayCout(float)
, the compiler will get upset:
error C2664: 'RenderValues' : cannot convert parameter 3 from 'void (__cdecl *)(double)' to 'DisplayFuncPtr' - Even though the return value of
func
is not used byRenderValues
, compiler will not allow any callback function returning non-void. - And this one troubles me a lot! The calling convention must also match. If function specifies
cdecl
as callback function, a function implemented as stdcall (__stdcall
), will not be allowed.
Since function-pointers and callbacks comes from the C language itself, compilers have to impose these restrictions. Compiler just cannot allow incorrect function to avoid call-stack to get corrupted.
And here is template based solution to overcome all the mentioned issues.
template<typename TDisplayFunc> void ShowValues(int nStart, int nEnd, TDisplayFunc func) { for(;nStart<=nEnd; ++nStart) func(nStart); // Display using the desired display-function }
You can happily supply any of the functions to ShowValues
templated-based function:
void DisplayWithFloat(float); int DisplayWithNonVoid(int); void __stdcall DisplayWithStd(int); ... ShowValues(1,20, DisplayWithFloat); ShowValues(1,40, DisplayWithNonVoid); ShowValues(1,50, DisplayWithStd);
Yes, you would get float
to int
conversion warning for first function. But return type and calling convention would not matter. In fact, any function that can be called with int
argument would be allowed in this case. You can modify the third function taking double, returning a pointer:
int* __stdcall DisplayWithStd(double);
The reason is simple. Actual type of TDisplayFunc
is determined at compile time, depending on the type-of argument passed. In case of function-pointer implementation, there is exactly one implementation. But in case of function templates, there would be different instantiations of ShowValues
, depending on unique function-prototypes you instantiate it with.
Along with the concerns mentioned above for normal C-style function-pointer/callback approach, following are also not allowed as display-function argument:
- Functors, i.e. Function objects - A class may implement
operator()
with required signature. For example:
struct DisplayHelper { void operator()(int nValue) { } };
The following code is illegal.
DisplayHelper dhFunctor; RenderValues(1,20,dhFunctor); // Cannot convert...
But when you pass the dhFunction
(a functor, aka function-object) to function template ShowValues
, the compiler will make no complains. As I said earlier TDisplayFunc
may be any type that can be called with int
argument.
ShowValues(1,20, dhFunctor);
- Lambdas - Locally defined functions (C++11 feature). Lambdas will also be not allowed as function-pointer argument to C-style function. Following is erroneous.
RenderValues(1,20, [](int nValue) { cout << nValue; } );
But it is perfectly valid for ShowValues
function template.
ShowValues(1,20, [](int nValue) { cout << nValue; });
Of-course, using lambda requires C++11 complaint compiler (VC10 and above, GCC 4.5 and above).
Interestingly, the function template may be crafted in different way - where you need not to pass a functor as function parameter. Instead, you can pass it as template argument itself.
template<typename TDisplayFunc> void ShowValuesNew(int nStart, int nEnd) { TDisplayFunc functor; // Create functor here for(;nStart<=nEnd; ++nStart) functor(nStart); } ... ShowValuesNew<DisplayHelper>(1,20); // 1 template, 2 function arguments
In this case, I have passed struct DisplayHelper
as template type argument. The function itself now takes only two arguments. The creation of functor is now done by template function itself. The only disadvantage is that you can now only pass struct or classes, having operator()
defined in it. You cannot pass a normal function to ShowValuesNew
. You can however pass a lambda's type using decltype
keyword.
auto DispLambda = [](int nValue) { printf("%d ", nValue); }; ShowValuesNew<decltype(DispLambda)>(1,20);
Since the type of any lambda is around std::function
, which is a class type, and hence object creation (TDisplayFunc
functor;
) is allowed.
By now, you have realized that function-pointer approach is very restrictive. The only advantage is code-size reduction and possibility of putting a function into some library, and later call that function passing different callbacks. The callable callback is truly late-bound. Since the core function is defined at one place, compiler does not have much of liberty to optimize the code based on functions (callbacks) passed, especially of core-function resides in other library (DLL/SO). Of course, if the core function is large, and restrictive nature is desired/acceptable, you would use function-pointer approach.
Template based approach, on the other side, do advocate for early-binding. Early binding is the core and heart of template based programming. As mentioned before, template code would generally not be intensive and big, like huge data-processing, a gaming engine, batch image processing, security subsystem - but a helper for all these systems. Therefore, early-bound nature actually helps in optimizing the code, since everything is under the compiler-territory.
Templates and Virtual Functions
Virtual functions and templates don't go together - they are into different leagues. Reason is simple - One employs late-binder and other one employs early-binder. Do very well remember that templates are compile-time, unlike generics in other managed languages (like C#). The type of a generic is determined at runtime, depending on how it is instantiated. But in case of templates, type is determined at compile time only. There are many other differences, pros and cons of templates and generics, but I am deferring that explanation.
Just for a logical understanding of this separation, consider following code.
class Sample { public: template<class T> virtual void Processor() // ERROR! { } };
It asks Sample::Processor<T>
method template to be virtual, which does not make any sense. How the sample class it to be used and inherited. So, for example, if you make new class SampleEx
and inherit it from Sample
, and attempt implement this virtual function. Which specialization would you override? A Processor<int>
or Processor<string>
, for example?
Since there are possibilities of infinite specializations of Processor
method that can be overridden, depending on how method-template Processor
is being called (via base of any of derived classes) - the virtual
keyword loses its meaning. Compiler cannot create virtual-function-table for such design. Also, compiler cannot enforce derived class to implement all those infinite implementations, if base class declares the given method-template as pure-virtual!
ATL library, from Microsoft, uses template based design on top of inheritance principle - but without virtual functions. For performance reasons, it uses templates, and not virtual functions - it means ATL uses more of static-binding, rather than dynamic-binding.
How would you utilize template based classes, having inheritance, but without virtual-functions? And yet facilitate that base class would know and call methods of derived class?
Before I explicate that feature, do remember that such classes will not be complete without derived classes. By this I don't mean abstract classes or pure-virtuals. Class templates, as you know, gets compiled only when instantiated with particular type - and this rule holds true for all of the methods in class. Similar way, base class will not be complete without its partner in crime -derived class. It also means that such classes cannot be exported from a library, whereas normal classes (even abstract) may be exported from a library.
Let me start it with normal inheritance model - a base class, having pure virtual function, and a derived class implementing it.
class WorkerCore { public: void ProcessNumbers(int nStart, int nEnd) { for (;nStart<=nEnd; ++nStart) { ProcessOne(nStart); } } virtual void ProcessOne(int nNumber) = 0; }; class ActualWorker : public WorkerCore { void ProcessOne(int nNumber) { cout << nNumber * nNumber; } }; ... WorkerCore* pWorker =new ActualWorker; pWorker->ProcessNumbers(1,200);
You know that WorkerCore
class is abstract, and a pointer of this type may point to derived class. ProcessOne
is the function that would be doing actual work. The binding with actual function (in ProcessNumbers
) depends where this
pointer is actually pointing. This is very-much utilizing late-binding feature of the language.
For this trivial task, you don't want heavy runtime penalty - you would prefer early-binding. And there the nifty feature, templates, come to rescue! Carefully understand the following code.
template<class TDerived> class WorkerCoreT { public: void ProcessNumbers(int nStart, int nEnd) { for (;nStart<=nEnd; ++nStart) { TDerived& tDerivedObj = (TDerived&)*this; tDerivedObj.ProcessOne(nStart); } } }; class ActualWorkerT : public WorkerCoreT<ActualWorkerT> { public: void ProcessOne(int nNumber) { cout << nNumber * nNumber; } };
First understand the bold ones:
TDerived
in base class: Specifies the actual type of derived class. The derived class, when inheriting, must specify it.- Typecasting in
ProcessNumbers
: Since we onlyWorkerCoreT
is actually aTDerived
object, we can safely typecastthis
toTDerived
. And then callProcessOne
method using the object-reference. <ActualWorkerT>
specification: The derived class itself tells the base that "Here I am". This line is important, otherwise type ofTDerived
would be wrong, and so the typecasting.
Important thing to know that ProcessOne
is not a virtual function, not even a regular member in base class. The base class just assumes it exists in derived class, and makes a call to it. If ProcessOne
doesn't exist in derived class, the compiler will simply raise an error:
- 'ProcessOne' : is not a member of 'ActualWorkerT'
Even though there is typecasting involved, there is no runtime penalty involved, no runtime polymorphism, function-pointer drama etc. The said function exists in derived class, is accessible from base class, and is not restricted to be void (int)
. It could be, as mentioned in function-pointers section, int (float)
, or anything else that can be called with int
parameter.
The only thing is that a pointer of type WorkerCoreT
cannot simply point to derived class, and make a successful call to ProcessOne
. And you can justify that such thing doesn't make sense - either take early binding, or late, not both.
STL - An Introduction
STL stands for Standard Template Library, which is a part of C++ Standard Library. From programmer's point of view, even though it is (optional) part of C++ Library, most of other features (classes, functions) are dependent on STL itself. As the "template" word suggests, STL is mainly on top of C++ templates - there are class templates and function templates.
STL contains set of collection classes for representing arrays, linked lists, trees, sets, maps etc. It also contains helper functions to act on container classes (like finding maximum, sum or a particular element), and other auxiliary functions. Iterators are important classes that allow iteration over collection classes. First let me give simple example.
vector<int> IntVector;
Here vector is a class template, which is functionally equivalent to arrays. It takes one (mandatory) argument - the type. The above statement declares IntVector
to be a vector<>
of type int
. Few points:
vector
, along with other elements of STL, comes understd
namespace.- To use vector, you need to include
vector
header (and not vector.h) vector
stores its elements in contiguous memory- meaning that any element can be directly accessed. Yes, very much same as array.
Stepped up example:
#include <vector> int main() { std::vector<int> IntVector; IntVector.push_back(44); IntVector.push_back(60); IntVector.push_back(79); cout << "Elements in vector: " << IntVector.size(); }
About the bold-marked content:
- The header that must be included to use
vector
class. - Namespace specification:
std
. vector::push_back
method is used to add elements tovector
. Initially there are no elements in vector, you insert usingpush_back
. Other techniques also exist, butpush_back
is paramount.- To determine current size (not capacity) of vector, we use
vector::size
method. Thus the program will display 3.
If you were to implement vector, you would implement it like:
template<typename Type> class Vector { Type* pElements; // Allocate dynamically, depending on demands. int ElementCount; // Number of elements in vector public: Vector() : pElements(NULL), ElementCount(0) {} size_t size() const { return ElementCount; }; void push_back(const Type& element); // Add element, allocate more if required. };
No rocket science here, you know all of it. Implementation of push_back
would be to allocate additional memory, if required, and set/add the element to given location. This rises obvious question: How much memory to allocate on each new-element insertion? And here comes the Capacity subject.
vector
also has, not frequently used method: capacity
. Capacity of vector
is currently allocated memory (in element count), and can be retrieved using this function. Initial capacity, or the additional memory allocated on each push_back
depends on implementation (how VC or GCC or other compiler vendors implement it). Method capacity
will always return more or equal value, than the size
method would return.
I request you to implement push_back
and capacity
methods. Add any more data members or methods you may want to add.
One major advantage of vector is that it can be used like standard array; except that the size of array (i.e. element count of vector) is not constant. It may vary. You may think of it as dynamically allocated array, where you allocate desired memory (re-allocate if needed), keep track of size of array, check for memory allocation failure and need to free memory at the end. std::vector
handles it all, yet for all data-types that meet the "Requirements of this class template".
Having said that vector is functionally equivalent to array, following is valid code. (Yes, there has to be at least 3 elements in vector for this code to work).
IntVector[0] = 59; // Modify First element cout << IntVector[1]; // Display Second element int *pElement = &IntVector[2]; // Hold Third element cout << *pElement; // Third
It clearly means that vector
has operator[]
overloaded, which is like:
Type& operator[](size_t nIndex) { return pElements[nIndex]; } const Type& operator[](size_t nIndex) { return pElements[nIndex]; }
I have not shown the basic validations here. Important to note the two overloads based on const
. Class std::vector
also has the two overloads - one which returns the reference of actual element, other one returns const-reference. Former will allow modification of actual element stored(See "Modify First element" comment above), and latter will not allow modification.
What about showing all the elements of a vector
? Well, the following code will work for vector<int>
:
for (int nIndex = 0 ; nIndex < IntVector.size(); nIndex++) { cout << "Element [" << nIndex << "] is " << IntVector[nIndex] << "\n"; }
Nothing important to explain here, until I explicate the flaws with this type of collection-iteration code. Anyway, we can utilize function template feature to write-up a function that can display any type of vector
. Here it is:
template<typename VUType> // Vector's Underlying type! void DisplayVector(const std::vector<VUType>& lcVector) { for (int nIndex = 0 ; nIndex < lcVector.size(); nIndex++) { cout << "Element [" << nIndex << "] is " << lcVector[nIndex] << "\n"; } }
Now this templated vector-iteration code can display any vector
-vector<float>
, vector<string>
or vector<Currency>
, as long as cout
can display the type, or the underlying type can make it cout-able. Please understand the bold-marked content yourself!
Following code is added only for better grip and understanding.
... IntVector.push_back(44); IntVector.push_back(60); IntVector.push_back(79); DisplayVector(IntVector); // DisplayVector<int>(const vector<int>&);
Would the implementation of DisplayVector
work for all type containers, like sets and maps? It won't! I will cover up it soon.
Another container in STL is set
. A set<>
would store only unique elements of type T
. You need to include <set>
header to use it. An example:
std::set<int> IntSet; IntSet.insert(16); IntSet.insert(32); IntSet.insert(16); IntSet.insert(64); cout << IntSet.size();
Usage is very similar to vector
, except that you need to use insert
method. Reason is simple and justified: New element may be placed anywhere in set
, not just at the end - and you can't force an element to be inserted at end.
The output of this code snipped will be 3, and not 4. Value 16 is being inserted twice, and set
will ignore the second insertion request. Only 16, 32 and 64 would exist in IntSet
.
Well, this article is not about STL, but about templates. I briefed about set
class also for a reason I am going to explain. You may find relevant documentation, articles, sample code etc on the net for STL. Use following keywords to search your favorites: vector
, map
, set
, multimap
, unordred_map
, count_if
, make_pair
, tuple
, for_each
etc.
Let me bring attention to the subject I have to elaborate.
How would you iterate through all elements of a set
? Following code is not going to work for set
.
for (int nIndex = 0; nIndex < IntSet.size(); nIndex) { cout << IntSet[nIndex]; // ERROR! }
Unlike vector
class, set
does not define operator[]
. You cannot access any element based on its index - the index doesn't exist for set
. The order of elements in set are ascending: from smaller to larger. There exist weak-strict ordering, comparer class etc, but lets consider his (ascending) as default behavior for the subject in hand.
So, at some point, if elements of set<int>
are (40,60,80), and later you insert 70, the sequence of elements would become (40, 60, 70, 80). Therefore, logically, index is inappropriate for set
.
And here comes another important facet of STL: Iterators. All container classes do have support for iterators, so that elements of collection can be iterated through. Different kind of iterators are represented by various classes. First let me present you a sample code to iterator a standard-array.
int IntArray[10] = {1,4,8,9,12,12,55,8,9}; for ( int* pDummyIterator = &IntArray[0]; // BEGIN pDummyIterator <= &IntArray[9]; // Till LAST element pDummyIterator++) { cout << *pDummyIterator << " "; }
Using simple pointer arithmetic, the code is displaying values of all elements of array. Similarly, iterators can be used to iterate a vector:
vector<int>::iterator lcIter;
for (lcIter = IntVector.begin();
lcIter != IntVector.end();
++lcIter)
{
cout << (*lcIter);
}
Carefully understand about the bold-marked content:
iterator
is a class. Specifically atypedef
insidevector<int>
. Thus, a variable of typevector<int>::iterator
may only iterate avector<int>
, and notvector<float>
orset<int>
. How exactly iterator istypedef
'd, shouldn't matter you or any STL programmer.begin
andend
are methods that returniterator
of same type. An instance ofvector<Currency>
would returnvector<Currency>::iterator
, when you invokebegin
orend
on it.begin
returns an iterator that points to the first element of container. Think it of as&IntArray[0]
.- Method
end
returns an iterator that points to the next-to-last element of container. Think it of as&IntArray[SIZE]
, whereIntArray
is ofSIZE
size. You know that, for size 10 array,&IntArray[10]
would be (logically) pointing to next element of&IntArray[9]
. - The expression
++lcIter
callsoperator++
on iterator object, which moves the iterator to point to the next element of collection. It is very much same as++ptr
pointer arithmetic,. - The loop starts with
iterator
pointing tobegin
, and goes till it points toend
.
- The expression
*lcIter
calls the unaryoperator*
on iterator, which returns the reference/const-reference of element currently pointed. For example above, it simply returnsint
.
You may not be able to grasp this complex iterator concept, so easily, so soon. You should regularly play with iterators - let the compiler bring you some weird errors, let your program crash down or disturb your debugger and cause assertions. More you bring these errors and assertions, the more you learn!
Exactly the same way, you may iterate a set
:
set<int>::iterator lcIter;
for (lcIter = IntSet.begin();
lcIter != IntSet.end();
++lcIter)
{
cout << (*lcIter);
}
If I ask you to write up iteration-loop for:
vector<float>
set<Currency>
vector<Pair>
Soon you would realize you need to change only container-type and/or the underlying type and rest of the code remain same! You may tempt to write a function template, which would take the container and the underlying-type as its template type arguments. Something like:
template<typename Container, typename Type> void DisplayCollection(const Container<Type>& lcContainer) { Container<Type>::iterator lcIter; for (lcIter = lcContainer.begin(); lcIter != lcContainer.end(); ++lcIter) { cout << (*lcIter); } }
Seems logically correct, but that's not going to compile. Similar to DisplayVector
function, this function attempts to take lcContainer
argument, having Container
as collection-class, and its underlying type as Type
. It won't be easy to understand why it won't work, but it's not that much odd to understand why it won't work.
The syntax of DisplayVector
was:
template<typename VUType> void DisplayVector(const std::vector<VUType>& lcVector)
Where the actual type being passed to function is complete expression: vector<VUType>&
. The type being passed was not just: vector&
The syntax of DisplayCollection
is something like:
template<typename Container, typename Type> void DisplayCollection(const Container<Type>& lcContainer)
Here the type being passed to function (not template) is complete: Container<Type>&
. Suppose if could call it as:
vector<float> FloatVector; DisplayCollection<vector, float>(FloatVector);
The (first) type being passed to template is just: vector
, which is not a complete type. Some specialization of vector
(like vector<float>
) would make it qualified for being a complete type. Since first template (type) argument cannot be classified as type, we cannot use it that way. Though there exist techniques to pass class-template itself (like just vector
), and make it complete type based on other arguments/aspects. Anyway, here is modified DisplayCollection
prototype:
template<typename Container> void DisplayCollection(const Container& lcContainer);
Yes, just that simple! But the implementation now demands some changes. So, lets implement it gradually.
template<typename Container> void DisplayCollection(const Container& lcContainer) { cout << "Items in collection: " << lcContainer.size() << "\n"; }
All STL containers do have size
method implemented, and they do return size_t
. So, irrespective which container is being passed (set
, map
, deque
etc) - the method size
will work.
The iteration of collection:
Container::const_iterator lcIter;
for (lcIter = lcContainer.begin();
lcIter != lcContainer.end();
++lcIter)
{
cout << (*lcIter);
}
Few things to learn:
Since the argument (
lcContainer
) is being passed with const
qualifier, it is rendered as non-mutable object in this function. It means you cannot insert, delete or (re)assign anything to the container. If a vector
is being passed, lcContainer.push_back
would be an error, since object is const. Further it means that you cannot iterate it using mutable iterator.
- Using
iterator
class, you can change the contents. It is thus referred as mutable-iterator. - Use
const_iterator
when you don't need to change, or you cannot use mutable-iterator. When object/container itself is const (non-mutable), you must useconst_iterator
. - Important! An object of
const_iterator
is not same as constant object ofiterator
.
That means:const_iterator != const iterator
- note the space!
How the compiler would return iterator
or const_iterator
, when I am calling same methods: begin
and end
?
Valid question, and simple answer:
class SomeContainerClass { iterator begin(); iterator end(); const_iterator begin() const; const_iterator end() const; };
When object is const
, the const
version of method is called - simple C++ rule!
Now, one more important point to consider. The code given above, will not compile on all compilers (specifically the following line):
Container::const_iterator lcIter
Visual C++ 2008 compiles it fine, but GCC reports following error:
To understand the reasoning, consider following class:
class TypeNameTest { public: static int IteratorCount; typedef long IteratorCounter; }; int main() { TypeNameTest::IteratorCounter = 10; // ERROR TypeNameTest::IteratorCount var; //ERROR }
Using ClassName::Symbol notation, we can access both typedef
symbol and and statically defined symbol. For simple classes, the compiler is able to distinguish, the programmer is able to resolve and there is no potential ambiguity.
But in case of template functions, where the the underlying type itself is dependent on template type argument (like const_iterator
being based on Container
), the compiler must be told that specified symbol is actually a type, and not a static symbol of given class. And there we use typename
keyword to classify the symbol as a type.
Therefore we should (must) use typename keyword:
typename Container::const_iterator lcIter; // const_iterator is a type, not static symbol in Container
Why can't we use class
keyword instead, and why does VC++ compile it fine?
Ah! Compiler vendors and their tendency to follow some non-standards. VC++ doesn't need typename
, GCC needs it (and tells you!). GCC will even accept class
in-place of typename
, VC will not accept class
. But thankfully, both adhere to the standards and do accept typename
keyword!
The new C++ standard (C++11) brings up some respite, specially while working with STL, templates and complicated iterator definitions. The auto
keyword. The iteration loop can be modified as:
for (auto lcIter = lcContainer.begin(); lcIter != lcContainer.end(); ++lcIter)
{
cout << (*lcIter);
}
The actual type of lcIter
will be determined automatically, at compile-time. You may read about auto
keyword on the Internet, your favorite author's book or may refer my article.
Templates and Library Development
As you know that the template code doesn't directly go to object file, it gets compiled (second phase compilation) only when instantiated with appropriate template parameters. Since the actual code generation (i.e. specialization) happens only by instantiating a function/class template with appropriate template-arguments, the function/class template cannot be exported through a library.
When you attempt to export a function template, such as DisplayCollection
, through a library (.LIB,
.DLL
or .SO
), the compiler and linker may depict that it exported the given function. Linker may throw a warning or error that some symbols (e.g. DisplayCollection
) was not exported or not found. Since there were no call to function template(s) in the library itself, no actual code was generated, and thus nothing actually was exported.
When you later use that library in other project, you would get set of linker errors that some symbols were not found. To recollect this problem, read this section again.
It is therefore not possible to export template code from a library, without disclosing the source code, and delivering it (generally via header-files). Though, it is very much possible to expose source-code for only the templated-stuff, and not the core library stuff, which may be playing with void-pointers, and sizeof
keyword. The core library may actually be made private by exporting it from the library, since core stuff may not be template-based.
External Templates, a feature still pending to be part of C++ standard, is not supported by major compilers.
Some libraries may export entire class for particular template arguments using Explicit Instantiation feature.
Explicit Instantiation
This feature is particularly important if you are exposing your template-based library, either by header-only implementation, or through wrapper-mode implementation (hiding core, but exposing features via templates). With Explicit Instantiation, you can instruct the compiler (and thus, the linker) to generate the code for specific template arguments. It means you are demanding a specialization of template, without actually instantiating it in your code. Consider a simple example.
template class Pair<int, int>;
This statement simply asks the compiler to instantiate Pair
with <int,int>
arguments for all method of Pair
class. That means, compiler will generate code for:
- Data-members -
first
andsecond
. - All three constructors (as mentioned in this and previous article).
- Operators
>
and==
(as mentioned).
To verify this, you may look at the generated binary (executable, DLL/SO), using appropriate tool On Windows, you may use Dependency Walker to see if code was generated or not. A simpler method exist to assert if compiler/linker are actually performing explicit instantiation - Let the compiler break on failure. For example:
template struct Pair<Currency, int>;
Would make the type of first
as Currency
. Compiler will attempt to generate code for all methods, and would fail on operator ==
, saying that it (Currency
) doesn't have operator defined:
bool operator == (const Pair<Type1, Type2>& Other) const { return first == Other.first && // ERROR Currency.operator== isn't available. second == Other.second; }
It fails on this method, just because it comes before any other method that fails (before operator<
in this case).
This was just the example to check if compiler is actually generating code for all methods or not. But the main question is: Why would you want to utilize this feature?
For example, you expose a string class (like std::string
, or CString
). And that class is on top of a template argument - the character type - ANSI or Unicode. A very simple definition of String
class template:
template<typename CharType> class String { CharType m_str[1024]; public: CharType operator[](size_t nIndex) { return m_str[nIndex]; } size_t GetLength() const { size_t nIndex = 0; while(m_str[nIndex++]); return nIndex; } };
And a pretty simple usage example:
String<char> str; str.GetLength();
And you know that it will produce only following:
String<char>::m_str
String<char>::String
- default compiler provided constructor.String<char>::GetLength
method
If you were to put String
into some library, you may put entire String
class into header, and ship the header. Here is question is not at all about private-stuff, encapsulation etc, it about unnecessary increase in size of different executables produced.
There would be thousands of binaries (DLLs, SOs, executables), and almost all of them would be using String
class. Wouldn't it be better if you could pack them into one library? Yes, I mean the non-templated traditional approach?
To do so, you just ask explicit instantiation for all types you are supposed to export through library.
template class String<char>; template class String<wchar_t>;
For programmer's convinience, you may typedef different String types. The std::string type is, in fact, typedef'd and exported in this manner:
typedef basic_string<char, ... > string; typedef basic_string<wchar_t, ... > wstring; // Explicit Instantiation template class /*ATTR*/ basic_string<char, ...>; template class /*ATTR*/ basic_string<wchar_t, ... >;
The base class is basic_string
, which is class template. Few arguments not shown here only for simplicity, and vendors may have different signature of reset of template arguments (for basic_string
). Second group shows explicit instantiations for these types. The commented part, /*ATTR*/
- would depend on compiler vendor. It may be an expression that these instantiations do actually go in library being compiled, or are only acting as header-only. In VC++ implementation, these two instantiations are actually in a DLL.
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
An Idiot's Guide to C++ Templates - Part 1
Prolusion
Most C++ programmers stay away from C++ templates due to their perplexed nature. The excuses against templates:
- Hard to learn and adapt.
- Compiler errors are vague, and very long.
- Not worth the effort.
Admitted that templates are slightly hard to learn, understand, and adapt. Nevertheless, the advantages we gain from using templates would outweigh the negatives. There is a lot more than generic functions or classes that can be wrapped around templates. I would explicate them.
While C++ templates and STL (Standard Template Library) are siblings, technically. In this article, I would only cover templates at the core level. Next parts of this series would cover more advanced and interesting stuff around templates, and some know-how about STL.
Table of Contents
The Syntax Drama
As you probably know, template largely uses the angle brackets: The less than ( <
) and the greater than ( >
) operators. For templates, they are always used together in this form:
< Content >
Where Content
can be:
class T
/typename T
- A data type, which maps to
T
- An integral specification
- An integral constant/pointer/reference which maps to specification mentioned above.
For point 1 and 2, the symbol T
is nothing but some data-type, which can be any data-type - a basic datatype (int
, double
etc), or a UDT.
Let's jump to an example. Suppose you write a function that prints double (twice) of a number:
void PrintTwice(int data)
{
cout << "Twice is: " << data * 2 << endl;
}
Which can be called passing an int
:
PrintTwice(120); // 240
Now, if you want to print double of a double
, you would overload this function as:
void PrintTwice(double data)
{
cout << "Twice is: " << data * 2 << endl;
}
Interestingly, class type ostream
(the type of cout
object) has multiple overloads for operator <<
- for all basic data-types. Therefore, same/similar code works for both int
and double
, and no change is required for our PrintTwice
overloads - yes, we just copy-pasted it. Had we used one of printf
-functions, the two overloads would look like:
void PrintTwice(int data)
{
printf("Twice is: %d", data * 2 );
}
void PrintTwice(double data)
{
printf("Twice is: %lf", data * 2 );
}
Here the point is not about cout
or print
to display on console, but about the code - which is absolutely same. This is one of the many situations where we can utilize the groovy feature provided by the C++ language: Templates!
Templates are of two types:
- Function Templates
- Class Templates
C++ templates is a programming model that allows plugging-in of any data-type to the code (templated code). Without template, you would need to replicate same code all over again and again, for all required data-types. And obviously, as said before, it requires code maintenance.
Anyway, here is the simplified PrintTwice
, utilizing templates:
void PrintTwice(TYPE data)
{
cout<<"Twice: " << data * 2 << endl;
}
Here, actual type of TYPE
would be deduced (determined) by the compiler depending on argument passed to the function. If PrintTwice
is called as PrintTwice(144);
it would be an int
, if you pass 3.14
to this function, TYPE
would be deduced as double
type.
You might be confused what TYPE
is, how the compiler is going to determine that this is a function template. Is TYPE
type defined using typedef
keyword somewhere?
No, my boy! Here we use the keyword template
to let compiler know that we are defining a function template.
Function Templates
Here is the templated function PrintTwice
:
template<class TYPE>
void PrintTwice(TYPE data)
{
cout<<"Twice: " << data * 2 << endl;
}
The first line of code:
template<class TYPE>
tells the compiler that this is a function-template. The actual meaning of TYPE
would be deduced by compiler depending on the argument passed to this function. Here, the name, TYPE
is known as template type parameter.
For instance, if we call the function as:
PrintTwice(124);
TYPE
would be replaced by compiler as int
, and compiler would instantiate this template-function as:
void PrintTwice(int data)
{
cout<<"Twice: " << data * 2 << endl;
}
And, if we call this function as:
PrintTwice(4.5547);
It would instantiate another function as:
void PrintTwice(double data)
{
cout<<"Twice: " << data * 2 << endl;
}
It means, in your program, if you call PrintTwice
function with int
and double
parameter types, two instances of this function would be generated by compiler:
void PrintTwice(int data) { ... }
void PrintTwice(double data) { ... }
Yes, the code is duplicated. But these two overloads are instantiated by the compiler and not by the programmer. The true benefit is that you need not to do copy-pasting the same code, or to manually maintain the code for different data-types, or to write up a new overload for new data-type that arrives later. You would just provide a template of a function, and rest would be managed by compiler.
It is also true that code size would increase, since there are now two function definitions. The code-size (at binary/assembly level) would almost be same. Effectively, for N number of data-types, N instances of same function (i.e. overloaded functions) would be created. There are advanced compiler/linker level optimizations which can somewhat reduce the code size, if instantiated functions are same, or some part of function body is same. I wouldn't discuss it now.
But, on a positive side, when you manually define N different overloads (say N=10
), those N different overloads would be anyway be compiled,linked and packed in binary (the executable). However, with templates, only the required instantiations of function would get into final executable. With templates, the overloaded copies of function might be less than N, and it can be more than N - but exactly the number of required copies - no more no less!
Also, for non-templated implementations, the compiler has to compile all those N copies - since they are in your source-code! When you attach template with a generic function, compiler would compile only for required set of data-types. It basically means the compilation would be faster if number of different data-types is less than N!
It would be a perfectly valid argument, that the compiler/linker would likely do all possible optimizations to remove unused non-template function' implementations from the final image. But, again, do understand that compiler has to compile all those overloads (for syntax checking etc). With templates, the compilation would happen only for required data-types - you can call it as "On demand compilation".
Enough of text-only content for now! You can come back and re-read it again. Let's move ahead.
Now, let's write another function template that would return the twice of given number:
template<typename TYPE>
TYPE Twice(TYPE data)
{
return data * 2;
}
You should have noticed that I used typename
, instead of class
. No, it is not required to use typename
keyword if a function returning something. For template programming, these two keywords are very much the same. There is a historical reason for having two keywords for same purpose, and I hate history.
However, there are instances where you can only use the newer keyword - typename
. (When a particular type is defined in another type, and is dependent on some template parameter - Let this discussion be deferred to another part).
Moving ahead. When we call this function as:
cout << Twice(10);
cout << Twice(3.14);
cout << Twice( Twice(55) );
Following set of functions would be generated:
int Twice(int data) {..}
double Twice(double data) {..}
Two things:
- In third line of code snipped above,
Twice
is called twice - the return value/type of first call would be the argument/type of second call. Hence, both calls are ofint
type (Since argument typeTYPE
, and return type are same). - If a template function is instantiated for a particular data-type, compiler would re-use the same function' instance - if the function is invoked again for same data-type. It means, irrespective of where, in your code, you invoke the function template with same type - in same function, in different function, or anywhere in another source file (of same project/build).
Let's write up a function template that would return the addition of two numbers:
template<class T>
T Add(T n1, T n2)
{
return n1 + n2;
}
Firstly, I just replaced template-type parameter's name - TYPE
with symbol T
. In template programming, you would generally use T
- but that's a personal choice. You should better use a name that reflects the meaning of type-parameter, and that improves code readability. This symbol can be any name which follows variable naming rules in the C++ language.
Secondly, I re-used the template parameter T
- for both of arguments (n1
and n2
).
Let's slightly modify Add
function, which would store the addition in local variable and then return the calculated value.
template<class T>
T Add(T n1, T n2)
{
T result;
result = n1 + n2;
return result;
}
Quite explanatory, I used the type parameter T
within the function's body. You might ask (you should): "How the compiler would know what is type of result
, when it tries to compile/parse the function Add
?"
Well, while looking at the body of function template (Add
), compiler would not see if T
(template type parameter) is correct or not. It would simply check for basic syntax (such as semi-colons, proper usage of keywords, matching braces etc), and would report errors for those basic checks. Again, it depends on compiler to compiler how it handles the template code - but it would not report any errors resulting due to template type parameters.
Just for completeness, I would reiterate, compiler will not check if (currently relevant only for function Add
):
T
is having a default constructor (so thatT result;
is valid)T
supports the usage ofoperator +
(so that<code>n1+n2
is valid)T
has an accessible copy/move-constructor (so thatreturn
statement succeeds)
Essentially, the compiler would have to compile the template code in two phases: Once for basic syntax checks; and later for each instantiation of function template - where it would perform actual code compilation against the template data-types.
It is perfectly okay if you did not completely understood this two phase compilation process. You would get firm understanding as you read through this tutorial, and then you would come back to read these theory-sessions later!
Pointers, References and Arrays with Templates
First a code sample (No worries - it is simple code snippet!):
template<class T>
double GetAverage(T tArray[], int nElements)
{
T tSum = T(); // tSum = 0
for (int nIndex = 0; nIndex < nElements; ++nIndex)
{
tSum += tArray[nIndex];
}
// Whatever type of T is, convert to double
return double(tSum) / nElements;
}
int main()
{
int IntArray[5] = {100, 200, 400, 500, 1000};
float FloatArray[3] = { 1.55f, 5.44f, 12.36f};
cout << GetAverage(IntArray, 5);
cout << GetAverage(FloatArray, 3);
}
For the first call of GetAverage
, where IntArray
is passed, compiler would instantiate this function as:
double GetAverage(int tArray[], int nElements);
And similarly for float
. The return type is kept as double
since average of numbers would logically fit in double
data-type. Note that this is just for this example - the actual data-type that comes under T
may be a class, which may not be converted to double
.
You should notice that a function template may have template type arguments, along with non-template type arguments. It it not required to have all arguments of a function template to arrive from template types. int nElements
is such function argument.
Clearly note and understand that template type-parameter is just T
, and not T*
or T[]
- compilers are smart enough to deduce the type int
from an int[]
(or int*
). In the example given above, I have used T tArray[]
as an argument to function template, and actual data-type of T
would intelligently be determined from this.
Most often, you would come across and would also require to use initialization like:
T tSum = T();
First thing first, this is not template specific code - this comes under C++ language itself. It essentially means: Call the default constructor for this datatype. For int
, it would be:
int tSum = int();
Which effectively initializes the variable with 0
. Similarly, for float
, it would set this variable to 0.0f
. Though not covered yet, if a user defined class type comes from T
, it would call the default constructor of that class (If callable, otherwise relevant error). As you can understand that T
might be any data-type, we cannot initialize tSum
simply with an integer zero (0
). In real case, it may be a some string class, which initializes it with empty string (""
).
Since the template type T
may be any type, it must also have += operator
available. As we know, it is available for all basic data types (int
, float
, char
etc.). If actual type (for T
), doesn't have +=
operator available (or any possibility), compiler would raise an error that actual type doesn't have this operator, or any possible conversion.
Similarly, the type T
must be able to convert itself to double
(see the return
statement). I will cover up these nitty-gritties, later. Just for better understanding, I am re-listing the required support from type T
(now, only applicable for GetAverage
function template):
- Must have an accessible default constructor.
- Must have
+= operator
callable. - Must be able to convert itself to
double
(or equivalent).
For GetAverage
function template prototype, you may use T*
instead of T[]
, and would mean the same:
template<class T>
GetAverage(T* tArray, int nElements){}
Since the caller would be passing an array (allocated on stack or heap), or an address of a variable of type T
. But, as you should be aware, these rules comes under rule-book of C++, and not specifically from template programming!
Moving ahead. Let's ask the actor 'reference' to come into template programming flick. Quite self-explanatory now, you just use T&
as a function template argument for the underlying type T
:
template<class T>
void TwiceIt(T& tData)
{
tData *= 2;
// tData = tData + tData;
}
Which calculates the twice value of argument, and puts into same argument' value. You would call it simply as:
int x = 40;
TwiceIt(x); // Result comes as 80
Note that I used operator *=
to get twice of argument tData
. You may also use operator +
to gain the same effect. For basic data-types, both operators are available. For class type, not both operators would be available, and you might ask the class' to implement required operator.
In my opinion, it is logical to ask operator +
be defined by class. The reason is simple - doing T+T
is more appropriate for most UDTs (User Defined Type), than having *= operator
. Ask yourself: What does it mean if some class String
or Date
implements, or is asked, to implement following operator:
void operator *= (int); // void return type is for simplicity only.
At this point, you now clearly understand that template parameter type T
may be inferred from T&
, T*
or T[]
.Therefore, it is also possible and very reasonable to add
const
attribute to the parameter which is arriving to function template, and that parameter would not be changed by function template. Take it easy, it is as simple as this:
template<class TYPE>
void PrintTwice(const TYPE& data)
{
cout<<"Twice: " << data * 2 << endl;
}
Observe that I modified the template parameter TYPE
to TYPE&
, and also added const
to it. Few or most of the readers would have realized the importance of this change. For those who didn't:
- The
TYPE
type may be large in size, and would demand more space on stack (call-stack). It includesdouble
which requires 8 bytes*
, some structure or a class, which would demand more bytes to be kept on stack. It essentially means - a new object of given type would be created, copy constructor called, and be put into call stack, followed by destructor call at then end of function.
Addition of reference (&
) avoids all this - reference of same object is passed. - Function would not change the argument passed, and therefore addition of
const
to it. It ensures, to the caller of function, that this function (herePrintTwice
), is not going to change the parameter's value. It also ensures a compiler error if, by mistake, the function itself tries to modify content of (constant) argument.
*
On 32-bit platform, function arguments would require 4-bytes minimum, and in multiple of 4-bytes. This means a char
or short
would require 4 bytes in call-stack. An 11-byte object, for example would require 12-bytes in stack.Similarly, for 64-bit platform, 8-bytes would be needed. An 11-byte object would require 16-bytes. Argument of type
double
would need 8-bytes.All pointers/references takes 4-bytes/8-bytes respectively on 32-bit/64-bit platform, and therefore passing
double
or double&
would mean the same for 64-bit platform.And similarly, we should change other function templates as:
template<class TYPE>
TYPE Twice(const TYPE& data) // No change for return type
{
return data * 2;
}
template<class T>
T Add(const T& n1, const T& n2) // No return type change
{
return n1 + n2;
}
template<class T>
GetAverage(const T tArray[], int nElements)
// GetAverage(const T* tArray, int nElements)
{}
Note that it is not possible to have reference and const
added to return type, unless we intend to return reference (or pointer) of original object that was passed to the function template. The following code exemplifies it:
template<class T>
T& GetMax(T& t1, T& t2)
{
if (t1 > t2)
{
return t2;
}
// else
return t2;
}
It is how we utilize return reference:
int x = 50;
int y = 64;
// Set the max value to zero (0)
GetMax(x,y) = 0;
Note that this is just for illustration, and you would rarely see or write such code. You may, however see such code and may need to write, if the returned object is reference of some UDT. In that case member access operator dot (.
) or arrow (->
) would follow the function call. Anyway, this function template returns the reference of object which wins the greater-than race. This, definitely, requires operator >
be defined by type T
.
You should have noticed, I have not added const
to any of two parameters passed. This is required; since function returns non-const reference of type T
. Had it been like:
T& GetMax(const T& t1, const T& t2)
At the return
statements, compiler would complain that t1
or t2
cannot be converted to non-const. If we add const
to return type also ( const T& GetMax(...)
), following line at call site would fail to compile:
GetMax(x,y) = 0;
Since const
object cannot be modified! You can definitely do forceful const/non-const typecasting, either in function or at call site. But that's a different aspect, a bad design and a non-recommended approach.
Multiple Types with Function Templates
Till now I have covered only one type as template type parameters. With templates, you may have more than one template-type parameters. It goes like:
template<class T1, class T2, ... >
Where T1
and T2
are type-names to the function template. You may use any other specific name, rather than T1
, T2
. Note that the usage of '...
' above does not mean that this template specification can take any number of arguments. It is just illustration that template may have any number of arguments.
(As with C++11 standard, templates would allow variable number of arguments - but that thing is out of topic, for now.)
Let's have a simple example taking two template parameters:
template<class T1, class T2>
void PrintNumbers(const T1& t1Data, const T2& t2Data)
{
cout << "First value:" << t1Data;
cout << "Second value:" << t2Data;
}
And we can simply call it as:
PrintNumbers(10, 100); // int, int
PrintNumbers(14, 14.5); // int, double
PrintNumbers(59.66, 150); // double, int
Where each call demands separate template instantiation for the first and second types being passed (or say inferred). Therefore, following three function template instances would be populated by compiler:
// const and reference removed for simplicity
void PrintNumbers(int t1Data, int t2Data);
void PrintNumbers(int t1Data, double t2Data);
void PrintNumbers(double t1Data, int t2Data);
Realize that second and third instantiations are not same, as T1
and T2
would infer different data-types (int
, double
and double
,int
). Compiler will not perform any automatic conversion, as it might do for normal function call - A normal function taking int
, for example, may be passed short
, or vice-versa. But with templates, if you pass short
- it is absolutely short
, not (upgraded to) int
. So, if you pass (short
, int
), (short
, short
), (long
, int
) - this would result in three different instantiations for PrintNumbers
!
In similar fashion, function templates may have 3 or more type parameters, and each of them would map to the argument types specified in function call. As an example, the following function template is legal:
template<class T1, class T2, class T3>
T2 DoSomething(const T1 tArray[], T2 tDefaultValue, T3& tResult)
{
...
}
Where T1
specifies the type of array that would be passed by caller. If array (or pointer) is not passed, compiler will render appropriate error. The type T2
is used as return type as well as second argument, which is passed by value. Type T3
is passed as reference (a non-const reference). This function template example given above is just haphazardly chosen, but is a valid function template specification.
By now, I have stepped-up and elaborated multiple template parameters. But for a reason, I am stepping-down to one parameter function. There is a reason for this, and you will understand it in no time.Assume that there is a function (non templated), which takes an int
argument:
void Show(int nData);
And you call it as:
Show( 120 ); // 1
Show( 'X' ); // 2
Show( 55.64 ); // 3
- Call 1 is perfectly valid since function takes
int
argument, and we are passing120
. - Call 2 is valid call since we are passing
char
, which will be promoted by compiler toint
. - Call 3 would demand demotion of value - compiler has to convert
double
toint
, and hence55
would be passed instead of55.64
. Yes, this will trigger appropriate compiler warning.
One solution is to modify the function such that it takes double
, where all three types can be passed. But that wouldn't support all types and may not fit in, or convertible to, double
. Therefore, you may write set of overloaded functions, taking appropriate types. Armed with knowledge, now, you would appreciate the importance of templates, and would ask to write it a function template instead:
template<class Type>
void Show(Type tData) {}
Of course, assuming all existing overloads of Show
were doing the same thing.
Alright, you know this drill. So, what's new in this which caused me to step-down?Well, what if you wanted to pass int
to function template Show
, but wish the compiler instantiates as if double
was passed?
// This will produce (instantiate) 'Show(int)'
Show ( 1234 );
// But you want it to produce 'Show(double)'
May seem illogical to demand this thing, as of now. But there is valid reason to demand such instantiation, which you will understand and appreciate soon!
Anyway, first see how to demand such absurd thing:
Show<double> ( 1234 );
Which instantiates the following template function (as you know):
void Show(double);
With this special syntax (Show<>()
), you are demanding compiler to instantiate Show
function for the type being explicitly passed, and asking the compiler not to deduce type by function argument.
Function Template - Template Function
Important! There is a difference between function template and template function.
A function template is body of a function that is bracketed around template
keyword, which is not an actual function, and will not be fully compiled by compiler, and is not accountable by the linker. At least one call, for particular data-type(s) is needed to instantiate it, and be put into accountability of compiler and linker. Therefore, the instance of function template Show
is instantiated as Show(int)
or Show(double)
.
A template function? Simply put, an "instance of a function template", which is produced when you call it, or cause it to get instantiated for particular data type. The instance of function-template is actually a valid function.
An instance of a function template (aka template-function) is not a normal function, under the umbrella of name-decoration system of compiler and linker. That means, an instance of function-template:
template<class T>
void Show(T data)
{ }
for template argument double
, it is not:
void Show(double data){}
but actually:
void Show<double>(double x){}
For long, I did not uncover this, only for simplicity, and now you know it! Use your compiler/debugger to find out the actual instantiation of a function template, and see the full prototype of a function in call-stack or generated code.
And hence, now you know the mapping between these two:
Show<double>(1234);
...
void Show<double>(double data); // Note that data=1234.00, in this case!
Explicit Template Argument Specification
Stepping back (up) to the multiple template argument discussion.
We have following function template:
template<class T1, class T2>
void PrintNumbers(const T1& t1Data, const T2& t2Data)
{}
And have following function calls, causing 3 different instances of this function template:
PrintNumbers(10, 100); // int, int
PrintNumbers(14, 14.5); // int, double
PrintNumbers(59.66, 150); // double, int
And what if you just needed only one instance - both arguments taking double
? Yes, you are willing to pass int
s and let them be promoted double
s. Coupled with the understanding you just gained, you would call this function-template as:
PrintNumbers<double, double>(10, 100); // int, int
PrintNumbers<double, double>(14, 14.5); // int, double
PrintNumbers<double, double>(59.66, 150); // double, int
Which would produce only the following template function:
void PrintNumbers<double, double>(const double& t1Data, const T2& t2Data)
{}
And the concept of passing template type parameters this way, from the call-site, is known as Explicit Template Argument Specification.
Why would you need explicit type specification? Well, there are multiple reasons:
- You want only specific type to be passed, and not let the compiler intelligently deduce one or more template argument types solely by the actual arguments (function parameters).
For example, there is one function template, max
, taking two arguments (via only one template type parameter):
template<class T>
T max(T t1, T t2)
{
if (t1 > t2)
return t1;
return t2;
}
And you attempt to call it as:
max(120, 14.55);
It would cause a compiler error, mentioning there is an ambiguity with template-type T
. You are asking the compiler to deduce one type, from two types! One solution is to change max
template so that it takes two template parameters - but you aren't author of that function template.
There you use explicit argument specification:
max<double>(120, 14.55); // Instantiates max<double>(double,double);
Undoubtedly notice and understand that I have passed explicit specification only for first template parameter, the second type is deduced from second argument of function call.
- When function-template takes template-type, but not from its function arguments.
A simple example:
template<class T>
void PrintSize()
{
cout << "Size of this type:" << sizeof(T);
}
You cannot call such function template simply as:
PrintSize();
Since this function template would require template type argument specification, and it cannot be deduced automatically by compiler. The correct call would be:
PrintSize<float>();
which would instantiate PrintSize
with float
template argument.
- When function template has a return type, which cannot be deduced from arguments, or when function template doesn't have any argument.
An example:
template<class T>
T SumOfNumbers(int a, int b)
{
T t = T(); // Call default CTOR for T
t = T(a)+b;
return t;
}
Which takes two int
s, and sums them up. Though, summing them in int
itself is appropriate, this function template gives opportunity to calculate the sum (using operator+
) in any type as required by caller. For example, the get the result in double
, you would call it as:
double nSum;
nSum = SumOfNumbers<double>(120,200);
The last two are just simplified examples for completeness, just to give you the hint where Explicit Template Argument Specification would fit in. There are more concrete scenarios where this explicitness would be needed, and would be covered in next part.
Default Arguments with Function Templates
For readers, who do know about default template type specification in template' arena - this is not about default template-type arguments. Default template-types, is anyway, not allowed with function-templates. For readers, who do not know about it, do not worry - this paragraph is not about default template type specification.
As you know, a C++ function may have default arguments. The default-ness may only go from right to left, meaning, if nth argument is required to be default, (n+1)th must also be default, and so on till last argument of function.
A simple example to explicate this:
template<class T>
void PrintNumbers(T array[], int array_size, T filter = T())
{
for(int nIndex = 0; nIndex < array_size; ++nIndex)
{
if ( array[nIndex] != filter) // Print if not filtered
cout << array[nIndex];
}
}
This function template would print, as you can guess, all numbers except which are filtered out by third argument: filter
. The last, optional function argument, is defaulted to default-value of type T
, which, for all basic type would mean zero. Therefore, when you call it as:
int Array[10] = {1,2,0,3,4,2,5,6,0,7};
PrintNumbers(Array, 10);
It would be instantiated as:
void PrintNumbers(int array[], int array_size, int filter = int())
{}
The filter
argument would be rendered as: int filter = 0
.
As obvious, when you call it as:
PrintNumbers(Array, 10, 2);
The third argument gets value 2
, and not the default value 0
.
It should be clearly understood that:
- Type
T
must have default constructor available. And of course, all operators as may be required by function body, for typeT
. - The default argument must be deducible from the other non-default types the template takes. In
PrintNumbers
example, type ofarray
would facilitate deduction forfilter
.
If not, you must use explicit template argument specification to specify type of default argument.
For sure, the default argument may not necessarily be default value for type T
(pardon the pun). It means, the default-argument may not always need to depend on default-constructor of type T
:
template<class T>
void PrintNumbers(T array[], int array_size, T filter = T(60))
Here, the default function argument doesn't use default-value for type T
. Instead, it uses value 60
. This, for sure, requires the type T
to have copy-constructor which accepts an int
(for 60
).
Finally, here comes an end to 'Function Templates' for this part of article. I assume you enjoyed reading and grasping these basics of function templates. Next part would cover more intriguing aspects of Template programming.
Class Templates
More often, you would design and use class templates, than function templates. In general, you use a class template to define an abstract type whose behavior is generic and is reusable, adaptable. While some text would start by giving example about data-structures like linked-lists, stacks, queues and similar containers. I would start with very basic examples, that are easy to understand.
Let's take a simple class, which sets, gets and prints the value stored:
class Item
{
int Data;
public:
Item() : Data(0)
{}
void SetData(int nValue)
{
Data = nValue;
}
int GetData() const
{
return Data;
}
void PrintData()
{
cout << Data;
}
};
One constructor which initializes Data
to 0
, Set and Get methods, and a method to print current value. Usage is also quite simple:
Item item1;
item1.SetData(120);
item1.PrintData(); // Shows 120
Nothing new for you, for sure! But when you need similar abstraction for other data-type, you need to duplicate code of entire class (or at least the required methods). It incurs code maintenance issues, increases code size at source code as well as at binary level.
Yes, I can sense your intelligence that I am going to mention C++ templates! The templated version of the same class in form of class template is as below:
template<class T>
class Item
{
T Data;
public:
Item() : Data( T() )
{}
void SetData(T nValue)
{
Data = nValue;
}
T GetData() const
{
return Data;
}
void PrintData()
{
cout << Data;
}
};
The class template declaration starts with same syntax as function templates:
template<class T>
class Item
Note that the keyword class
is used two times - firstly to specify template type specification (T
), and secondly to specify that this is a C++ class declaration.
To completely turn Item
into a class template, I replaced all instances of int
with T
. I also used T()
syntax to call default constructor of T
, instead of hard-coded 0
(zero), in the constructor's initializer' list. If you've read function templates section completely, you know the reason!
And usage as also quite simple:
Item<int> item1;
item1.SetData(120);
item1.PrintData();
Unlike function template instantiation, where arguments of function itself helps the compiler to deduce template type arguments, with class templates you must explicitly pass template type (in angle brackets).
The code snippet shown above causes class template Item
to instantiate as Item<int>
. When you create another object with different type using Item
class template as:
Item<float> item2;
float n = item2.GetData();
It would cause Item<float>
to get instantiated. It is important to know that there is absolutely no relation between two instantiations of class template - Item<int>
and Item<float>
. For the compiler and linker, these two are different entities - or say, different classes.
First instantiation with type int
produces following methods:
Item<int>::Item()
constructorSetData
andPrintData
methods for typeint
Similarly, second instantiation with type float
would produce:
Item<float>::Item()
constructorGetData
method forfloat
type
As you know Item<int>
and Item<float>
are two different classes/types; and therefore, following code will not work:
item1 = item2; // ERROR : Item<float> to Item<int>
Since both types are different, the compiler will not call possible default assignment operator. Had item1
and item2
were of same types (say both of Item<int>
), the compiler would happily call assignment operator. Though, for the compiler, conversion between int
and float
is possible, it is not possible for different UDT conversions, even if underlying data members are same - this is simple C++ rule.
At this point, clearly understand that only following set of methods would get instantiated:
Item<int>::Item()
- constructorvoid Item<int>::SetData(int)
methodvoid Item<int>::PrintData() const
methodItem<float>::Item()
- constructorfloat Item<float>::GetData() const
method
The following methods will not get second phase compilation:
int Item<int>::GetData() const
void Item<float>::SetData(float)
void Item<float>::PrintData() const
Now, what is a second phase compilation? Well, as I already elaborated that template-code would be compiled for basic syntax checks, irrespective of it being called/instantiated or not. This is known as first-phase compilation.
When you actually call, or somehow trigger it to be called, the function/method for the particular type(s) - then only it gets special treatment of second-phase compilation. Only through the second phase compilation, the code actually gets fully-compiled, against the type for which it is being instantiated.
Though, I could have elaborated this earlier, but this place is appropriate. How do you find out if function is getting first-phase and/or second-phase compilation?
Let's do something weird:
T GetData() const
{
for())
return Data;
}
There is an extra parenthesis at the end of for
- which is incorrect. When you compile it, you would get host of errors, irrespective of it being called or not. I have checked it using Visual C++ and GCC compilers, and both complain. This validates first-phase compilation.
Let's slightly change this to:
T GetData() const
{
T temp = Data[0]; // Index access ?
return Data;
}
Now compile it without calling GetData
method for any type - and there won't be any quench from the compiler. This means, at this point, this function doesn't get phase-two compilation treatment!
As soon as you call:
Item<double> item3;
item2.GetData();
you would get error from compiler that Data
is not an array or pointer, which could have operartor []
attached to it. It proves that only selected functions would get special privilege of phase-two compilation. And this phase-two compilation would happen separately for all unique types you instantiate class/function template for.
One interesting thing, you can do is:
T GetData() const
{
return Data % 10;
}
Which would get successfully compiled for Item<int>
, but would fail for Item<float>
:
item1.GetData(); // item1 is Item<int>
// ERROR
item2.GetData(); // item2 is Item<float>
Since operator %
is not applicable for float
type. Isn't it interesting?
Multiple Types with Class Templates
Our first class-template Item
had only one template type. Now let's construct a class that would have two template-type arguments. Again, there could have been somewhat complex class template example, I would like to keep it simple.
At times, you do require some native structure to keep few data members. Crafting a unique struct
for the same appears somewhat needless and unnecessary-work. You would soon come out of names for different structures having few members in it. Also, it increases code length. Whatever your perspective may be for this, I am using it as an example, and deriving a class template having two members in it.
STL programmers would find this as equivalent to std::pair
class template.
Assume you have a structure Point
,
struct Point
{
int x;
int y;
};
which is having two data-members. Further, you may also have another structure Money
:
struct Money
{
int Dollars;
int Cents;
};
Both of these structures have almost similar data-members in it. Instead of re-writing different structures, wouldn't it be better to have it at one place, which would also facilitate:
- Constructor having one or two arguments of given types, and a copy-constructor.
- Methods to compare two objects of same type.
- Swapping between two types
- And more.
You might say you can use inheritance model, where you'd define all required methods and let derive class customize it. Does it fit in? What about the data-types you chosen? It might be int
, string
, or float
, some-class as types. In short, inheritance will only complicate the design, and will not allow plug-in feature facilitated by C++ templates.
There we use class templates! Just define a class template for two types, having all required methods. Let's start!
template<class Type1, class Type2>
struct Pair
{
// In public area, since we want the client to use them directly.
Type1 first;
Type2 second;
};
Now, we can use Pair
class template to derive any type having two members. An example:
// Assume as Point struct
Pair<int,int> point1;
// Logically same as X and Y members
point1.first = 10;
point1.second = 20;
Understand that type of first
and second
are now int
and int
, respectively. This is because we instantiated Pair
with these types.
When we instantiate it like:
Pair<int, double> SqRoot;
SqRoot.first = 90;
SqRoot.second = 9.4868329;
first
would be of int
type, and second
would be of double
type. Clearly understand that first
and second
are data-members, and not functions, and therefore there is no runtime penalty of assumed function call.
Note: In this part of article, all definitions are within the class declaration body only. In next part, I would explain how to implement methods in separate implementation file, and issues related with that. Therefore, all method definitions shown should be assumed within class ClassName{...};
only.
The following given default constructor would initialize both members to their default values, as per data type of Type1
and Type2
:
Pair() : first(Type1()), second(Type2())
{}
Following is a parameterized constructor taking Type1
and Type2
to initialize values of first
and second
:
Pair(const Type1& t1, const Type2& t2) :
first(t1), second(t2)
{}
Following is a copy-constructor which would copy one Pair
object from another Pair
object, of exactly same type:
Pair(const Pair<Type1, Type2>& OtherPair) :
first(OtherPair.first),
second(OtherPair.second)
{}
Please note that it is very much required to specify template type arguments of Pair<>
, for the argument of this copy-constructor. The following specification wouldn't make sense, since Pair
is not a non-template type:
Pair(const Pair& OtherPair) // ERROR: Pair requires template-types
And here is an example using parametrized constructor and copy-constructor:
Pair<int,int> point1(12,40);
Pair<int,int> point2(point1);
It is important to note that if you change any of the template type parameters of either of the objects, point2
or point1
,
you wouldn't be able to copy-construct it using point1
object. Following would be an error:
Pair<int,float> point2(point1); // ERROR: Different types, no conversion possible.
Though, there is a possible conversion between float
to int
, but there is no possible conversion between Pair<int,float>
to Pair<int,int>
. The copy constructor cannot take other type as copyable object. There is a solution to this, but I would discuss it in next part.
In similar fashion, you can implement comparison operators to compare two objects of same Pair
type. Following is an implementation of equal-to operator:
bool operator == (const Pair<Type1, Type2>& Other) const
{
return first == Other.first &&
second == Other.second;
}
Note that I used const
attribute, for argument and for the method itself. Please fully understand the first line of above' method definition!
Just like copy-constructor call, you must pass exactly the same type to this comparison operator - compiler will not attempt to convert different Pair
types. An example:
if (point1 == point2) // Both objects must be of same type.
...
For a solid understanding for the concepts covered till here, please implement following methods by on your own:
- All remaining 5 relational operators
- Assignment operator
Swap
method- Modify both constructors (except copy-constructor), and combine them into one so that they take both parameters as default. This means, implement only one constructor that can take 0,1 or 2 arguments.
Pair
class is an example for two types, and it can be used instead of defining multiple structures having just two data-members. The drawback is just with remembering what first
and second
would mean (X or Y?). But when you well-define a template instantiation, you would always know and use first
and second
members appropriately.
Ignoring this one disadvantage, you would achieve all the features in the instantiated type: constructors, copy-constructor, comparison operators, swap method etc. And, you'd get all this without re-writing the required code for various two-member structures you would need. Furthermore, as you know, only the set of required methods would get compiled and linked. A bug fix in class template would automatically be reflected to all instantiations. Yes, a slight modification to class template may also raise bunch of errors, of other types, if the modification fails to comply with existing usage.
Likewise, you can have a class template tuple
which allows three (or more) data-members. Please try to implement class tuple
with three members (first
, second
, third
) by yourself:
template<class T1, class T2, class T3>
class tuple
Non-type Template Arguments
Alright, we have seen that class templates, just like function templates, can take multiple type arguments. But class templates also allow few non-type template arguments. In this part, I will elaborate only one non-type: integer.
Yes, a class template may take a integer as template argument. First a sample:
template<class T, int SIZE>
class Array{};
In this class template declaration, int SIZE
is a non-type argument, which is an integer.
- Only integral data-types can be non-type integer argument, it includes
int
,char
,long
,long long
,unsigned
variants andenum
s. Types such asfloat
anddouble
are not allowed. - When being instantiated, only compile time constant integer can be passed. This means
100
,100+99
,1<<3
etc are allowed, since they are compiled time constant expressions. Arguments, that involve function call, likeabs(-120)
, are not allowed.
As a template argument, floats/doubles etc may be allowed, if they can be converted to integer.
Fine. We can instantiate class template Array
as:
Array<int, 10> my_array;
So what? What's the purpose of SIZE
argument?
Well, within the class template you can use this non-type integer argument, wherever you could have used an integer. It includes:
- Assigning static const data-member of a class.
template<class T, int SIZE>
class Array
{
static const int Elements_2x = SIZE * 2;
};
[First two lines of class declaration will not be shown further, assume everything is within class' body.]
Since it is allowed to initialize a static-constant-integer within class declaration, we can use non-type integer argument.
- To specify default value for a method.
(Though, C++ also allows any non-constant to be a default parameter of a function, I have pointed this one for just for illustration.)
void DoSomething(int arg = SIZE);
// Non-const can also appear as default-argument...
- To define the size of an array.
This one is important, and non-type integer argument is often used for this purpose. So, let's implement the class template Array
utilizing SIZE
argument.
private:
T TheArray[SIZE];
T
is the type of array, SIZE
is the size (integer) - as simple as that. Since the array is in private area of class, we need define several methods/operators.
// Initialize with default (i.e. 0 for int)
void Initialize()
{
for(int nIndex = 0; nIndex < SIZE; ++nIndex)
TheArray[nIndex] = T();
}
For sure, the type T
must have a default constructor and an assignment operator. I will cover these things (requirements) for function template and class templates in next part.
We also need to implement array-element access operators. One of the overloaded index-access operator sets, and the other one gets the value (of type T
):
T operator[](int nIndex) const
{
if (nIndex>0 && nIndex<SIZE)
{
return TheArray[nIndex];
}
return T();
}
T& operator[](int nIndex)
{
return TheArray[nIndex];
}
Note that first overload (which is declared const
) is get/read method, and has a check to see if index is valid or not, otherwise returns default value for type T.
The second overload returns the reference of an element, which can be modified by caller. There is no index validity check, since it has to return a reference, and therefore local-object (T()
) cannot be returned. You may, however, check the index argument, return default value, use assertion and/or throw an exception.
Let's define another method, which would logically sum all the elements of Array
:
T Accumulate() const
{
T sum = T();
for(int nIndex = 0; nIndex < SIZE; ++nIndex)
{
sum += TheArray[nIndex];
}
return sum;
}
As you can interpret, it requires operator +=
to be available for target type T
. Also note that return type is T
itself, which is appropriate. So, when instantiate Array
with some string class, it will call +=
on each iteration and would return the combined string. If target type doesn't have this +=
operator defined, and you call this method, there would be an error. In that case, you either - don't call it; or implement the required operator overload in the target class.
Template Class as Argument to Class Template
While it is a vague statement to understand, and invites some ambiguities, I would attempt my best to remove the fogginess.
First, recollect the difference between template-function and a function-template. If the neurons have helped to transfer the correct information to the cache of your brain-box, you now callback that template-function is an instance of function-template. If search-subsystem of your brain is not responding, please reload the information again!
An instance of class template is template class. Therefore, for following class template:
template<class T1, class T2>
class Pair{};
The instantiation of this template is a template-class:
Pair<int,int> IntPair;
Clearly understand that IntPair
is not a template-class, is not instantiation for class template. It is an object of a particular instantiation/class-template. The template-class/instantiation is Pair<int,int>
, which produces another class type (compiler, our friend does this, you know!). Essentially this is what template-class would be produced by compiler for this case:
class Pair<int,int>{};
There is more precise definition for a template-class, select this single line of code for easy understanding. Detailed explication would come in next installment of this series.
Now, let's come to the point. What if you pass a template-class to some class-template? I mean, what does it mean by following statement?
Pair<int, Pair<int,int> > PairOfPair;
Is it valid - if so, what does it mean?
Firstly, it is perfectly valid. Secondly, it instantiates two template classes:
Pair<int,int>
- APair<int, Pair<int,int> >
-- B
Both A and B types would be instantiated by compiler, and if there is any error, arising due to any type of these two template classes, compiler would report. To simplify this complex instantiation, you may do:
typedef Pair<int,int> IntIntPair;
...
Pair<int, IntIntPair> PairOfPair;
You can assign first
and second
members of PairOfPair
object like this:
PairOfPair.first = 10;
PairOfPair.second.first = 10;
PairOfPair.second.second= 30;
Note that second
member in last two lines is of type Pair<int,int>
, and therefore it has same set of members to be accessed further. That's the reason first
and second
members can be used, in cascaded manner.
Now you (hopefully) understand that class template (Pair
) is taking template-class (Pair<int,int>
)
as argument and inducing the final instantiation!
An interesting instantiation, in this discussion, would be of Array
with Pair
! You know that Pair
takes two template type arguments, and Array
takes one type argument, and a size (integer) argument.
Array< Pair<int, double>, 40> ArrayOfPair;
Here int
and double
are type-arguments for Pair
. Hence, the first template-type of Array
(marked bold) is Pair<int,double>
. The second argument is constant 40
. Can you answer this: Would the constructor of Pair<int,double>
be called? When it will be called? Before you answer that, I just reverse the instantiation as:
Pair<int, Array<double, 50>> PairOfArray;
Wohoo! What does it mean?
Well, it means: PairOfArray
is an instantiation of Pair
, which is taking first type as int
(for first
member), and second type (second
) is an Array
. Where Array
(the second type of Pair
) is 50
elements of type double
!
Don't kill me for this! Slowly and clearly understand these basic concepts of templates. Once you get the crystal-clear understanding, you would love templates!
Here again, I used a template-class (Array<double,50>
) as an argument to an instance of other type (Pair<int,...>
).
Okay, but what right-shift operator (>>
) is doing above? Well, that's not an operator, but just ending of Array
's type specification, followed by ending of Pair
type specification. Some old compilers required us to put a space in between two greater-than symbols, so to avoid error or confusion.
Pair<int, Array<double, 50> > PairOfArray;
At present, almost all modern C++ compilers are smart enough to understand that this is used to end template type specification, and therefore you need not to worry. Therefore, you may freely use two or more >
symbols to end template specification(s).
Kindly note that passing a template class (instantiation) is nothing very specific in C++ terms - it is just a type that a class template would take.
Finally, Here I put usage examples both objects. First the constructors.
Array< Pair<int, double>, 40> ArrayOfPair;
This will cause the constructor of Pair
to be called 40 times, since there is declaration of constant-size array in Array
class template:
T TheArray[SIZE];
Which would mean:
Pair<int,double> TheArray[40];
And hence the required number of calls to constructor of Pair
.
For the following object construction:
Pair<int, Array<double, 50>> PairOfArray;
The constructor of Pair
would initialize first argument with 0
(using int()
notation), and would call constructor of Array
with Array()
notation, as shown below:
Pair() : first(int()), second(Array())
{}
Since the default constructor of Array
class template is provided by compiler, it would be called. If you don't understand the stuff written here, please sharpen your C++ skills.
Assigning one element of ArrayOfPair
:
ArrayOfPair[0] = Pair<int,double>(40, 3.14159);
Here, you are calling non-const version of Array::operator[]
, which would return the reference of first element of Array
(from TheArray
). The element, as you know, is of type Pair<int,double>
. The expression on right side of assignment operator is just calling the constructor for Pair<int,double>
and passing required two arguments. The assignment is done!
Default Template Arguments with Class Templates
First let me eliminate any ambiguity with 'Default Argument' phrase. The same phrase was used in Function Template section. In that sub-section, the default-argument referred to arguments of function parameters itself, not the type-arguments of function template. Function templates, anyway, do not support default arguments for template-arguments. As a side note, please know that methods of a class template can take default arguments, as any ordinary function/method would take.
Class templates, on other hand, do support default-argument for the type/non-type arguments for the template parameters. Throwing you an example:
template<class T, int SIZE=100>
class Array
{
private:
T TheArray[SIZE];
...
};
I have just modified around SIZE
in first line for class template Array
. The second template parameter, an integer constant specification, is now set to 100
. It means, when you use it in following manner:
Array<int> IntArray;
It would essentially mean:
Array<int, 100> IntArray;
Which would be automatically placed by compiler during the instantiation of this class template. Of course, you can specify custom array size by explicitly passing the second template argument:
Array<int, 200> IntArray;
Do remember that when you explicitly pass the default argument's parameter, with the same argument specified in class template declaration, it would instantiate it only once. By this, I mean, the following two objects created would instantiate only one class: Array<int,100>
Array<int> Array1;
Array<int,100> Array2;
Of course, if you change the default' template parameter in class template definition, with value other than 100
, it would cause two template instantiations, since they would be different types.
You can customize the default argument by using const
or #define
:
const int _size = 120;
// #define _size 150
template<class T, int SIZE=_size>
class Array
For sure, using _size
symbol instead of hard-coded constant value mean the same. But using a symbol would ease the default' specification. Irrespective of how you specify the default template parameter for integer (which is a non-type template argument), it must be a compile time constant expression.
You would generally not use default specification for non-type integer parameter, unless you are utilizing templates for advanced stuff, like meta-programming, static-asserts, SFINAE etc, which definitely demands a separate part. More often you would see and implement default parameters for class templates, which are data-types. An example:
template<class T = int>
class Array100
{
T TheArray[100];
};
It defines an array of type T
of size 100
. Here, the type argument is defaulted to int
. That means, if you don't specify the type while instantiating Array100
, it would map to int
. Following is an example on how to use it:
Array100<float> FloatArray;
Array100<> IntArray;
In the first instantiation, I passed float
as template type, and in second call I kept it default (to int
), by using <>
notation. While there are more uses of this notation in template programming, which I would cover up in later parts, it is very much required for this case also. If you try to use the class template as:
Array100 IntArray;
It would result in compiler errors, saying Array100
requires template parameters. Therefore, you must use empty-set of angle-brackets (<>
) to instantiate a class template, if all template arguments are default, and you wish to use defaults.
Important thing to remember is that a non-template class of name Array100
will not be allowed also. Definition of a non-template class like as given below, along with template class (above or below each other), will upset the compiler:
class Array100{}; // Array100 demands template arguments!
Now, let's mix both type and non-type argument in our class Array
:
template<class T = int, int SIZE=100>
class Array
{
T TheArray[SIZE];
...
};
Finally, both type and the size arguments are marked default with int
and 100
respectively. Clearly understand that first int
is for default specification of T
, and second int
is for non-template constant specification. For simplicity and better readability, you should keep them in different lines:
template<class T = int,
int SIZE=100>
class Array{};
Now, use use your intelligence to parse the meaning of following instantiations:
Array<> IntArray1;
Array<int> IntArray2;
Array<float, 40> FlaotArray3;
Just like explicit specification in function templates, specifying only the trailing template arguments is not allowed. Following is an error:
Array<, 400> IntArrayOf500; // ERROR
As a final note, do remember that following two object creations would instantiate only one class template, since essentially they are exactly the same:
Array<> IntArray1;
Array<int> IntArray2
Array<int, 100> IntArray3;
Defaulting a template type on another type
It is also possible to default a type/non-type parameter on a previously arrived template parameter. For instance, we can modify the Pair
class so that second type would be same as first type, if second type is not explicitly specified.
template<class Type1, class Type2 = Type1>
class Pair
{
Type1 first;
Type2 second;
};
In this modified class template Pair
, Type2
now defaults to Type1
type. An instantiation example:
Pair<int> IntPair;
Which, as you can guess, is same as:
Pair<int,int> IntPair;
But saves you from typing the second parameter. It is also possible to let the first argument of Pair
be default also:
template<class Type1=int, class Type2 = Type1>
class Pair
{
Type1 first;
Type2 second;
};
Which means, if you don't pass any template argument, Type1
would be int
, and hence Type2
would also be int
!
The following usage:
Pair<> IntPair;
Instantiates following class:
class Pair<int,int>{};
For sure, it is also possible to default non-type arguments on another non-type argument. An example:
template<class T, int ROWS = 8, int COLUMNS = ROWS>
class Matrix
{
T TheMatrix[ROWS][COLUMNS];
};
But, the dependent template parameter must be on the right of which it is dependent upon. Following would cause errors:
template<class Type1=Type2, class Type2 = int>
class Pair{};
template<class T, int ROWS = COLUMNS, int COLUMNS = 8>
class Matrix
Class' Methods as Function Templates
Though, this one isn't for absolute beginners, but since I have covered both function templates and class templates - the elaboration of this concept is logical for this first part of this series itself.
Consider a simple example:
class IntArray
{
int TheArray[10];
public:
template<typename T>
void Copy(T target_array[10])
{
for(int nIndex = 0; nIndex<10; ++nIndex)
{
target_array[nIndex] = TheArray[nIndex];
// Better approach:
//target_array[nIndex] = static_cast<T>(TheArray[nIndex]);
}
}
};
The class IntArray
is simple, non-template class, having an integer array of 10
elements. But the method Copy
is a designed as a function template (method template?). It takes one template type parameter, which would be deduced by compiler automatically. Here is how we can use it:
IntArray int_array;
float float_array[10];
int_array.Copy(float_array);
As you can guess, IntArray::Copy
would be instantiated with type float
, since we are passing float-array to it. To avoid confusion and understand it better, just think of int_array.Copy
as Copy
only, and IntArray::Copy<float>(..)
as Copy<float>(..)
only. The method template of a class is nothing but an ordinary function template embedded in a class.
Do notice that I used 10
as array size everywhere. Interestingly, we can also modify the class as:
template<int ARRAY_SIZE>
class IntArray
{
int TheArray[ARRAY_SIZE];
public:
template<typename T>
void Copy(T target_array[ARRAY_SIZE])
{
for(int nIndex = 0; nIndex<ARRAY_SIZE; ++nIndex)
{
target_array[nIndex] = static_cast<T>(TheArray[nIndex]);
}
}
};
Which makes the class IntArray
and the method Copy
, better candidates to be in the realm of template programming!
As you would have intelligently guessed, Copy
method is nothing but an array-conversion routine, which converts from int
to any type, wherever conversion from int
to given type is possible. This is one of the valid case where class method can be written as function templates, taking template arguments by themselves. Please modify this class template so that it can work for any type of array, not just int.
For sure, 'explicit template argument specification' with method template is also possible. Consider another example:
template<class T>
class Convert
{
T data;
public:
Convert(const T& tData = T()) : data(tData)
{ }
template<class C>
bool IsEqualTo( const C& other ) const
{
return data == other;
}
};
Which can be utilized as:
Convert<int> Data;
float Data2 = 1 ;
bool b = Data.IsEqualTo(Data2);
It instantiates Convert::IsEqualTo
with float
parameter. Explicit specification, as given below, would instantiate it with double
:
bool b = Data.IsEqualTo<double>(Data2);
One of the astounding thing, with the help of templates, you can do it by defining conversion operator on top of template!
template<class T>
operator T() const
{
return data;
}
It would make possible to convert the Convert
' class template instance into any type, whenever possible. Consider following usage example:
Convert<int> IntData(40);
float FloatData;
double DoubleData;
FloatData = IntData;
DoubleData = IntData;
Which would instantiate following two methods (fully qualified names):
Convert<int>::operator<float> float();
Convert<int>::operator<double> double();
On one hand it provides good flexibility, since without writing extra code, Convert
can convert itself (the specific instantiation) to any data-type - whenever conversion is possible at compilation level. If conversion is not possible, like from double
to string-type, it would raise an error.
But on the other hand, it also invites trouble by possibility of inadvertently inserting bugs. You may not want to have conversion operator called, and it is called (compiler code generated) without you knowing about it.
At The End
You have just seen slight glimpse of power and flexibility provided by templates. Next part will cover more of advanced and intriguing concepts. My humble and aspiring request to all readers is to play more and more with templates. Try to gain firm understanding on one aspect first (like function template only), rather than just hastily jumping to other concept. Initially do it with your test projects/code-base, and not with any existing/working/production code.
Following is a summary of what we have covered:
- To avoid unnecessary code duplication and code maintenance issues, specially when code is exactly same, we can use templates. Templates are far better approach than using C/C++ macros or functions/classes running on top of void-pointers.
- Templates are not only type-safe, but also reduces unnecessary code-bloat which would not be referred (not generated by compiler).
- Function templates are used to put a code that is not a part of class, and is same/almost-same for different data-types. At most of the places, compiler would automatically determine the type. Otherwise you have to specify the type, or you may also specify explicit type yourself.
- Class templates makes it possible to wrap any data type around specific implementation. It may be an array, string, queue, linked-list, thread-safe atomic implementation etc. Class templates do facilitate default template type specification, which function template don't support.
Hope you have enjoyed the article, and cleared out the mental-block that templates are complicated, unnecessarily, bizarre. Second part would be arriving soon!
History
- First release: October 8, 2011 - Covered basics of templates in C++, function templates.
- First amendment: Oct 9, 2011 - Class Templates and multiple types in class templates, non-template type
- Third amendment: Oct 11, 2011 - Template class as argument to class template
- Fourth amendment: Oct 12, 2011 - Default Template Arguments with Class Templates
- Fifth amendment: Oct 13, 2011 - Methods as Function Templates, finishing lines.
- Sixth amendment: Mar 8, 2012 - Basic corrections.
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)