Programming Workshop 2 (CSCI 1061U)
Faculty of Science, Ontario Tech University
Templates are an important feature of C++. C++ std library and other libraries rely heavily upon templates.
I provide some examples of templates in different contexts (functions, classes, and templates with dynamic memory) at the end of this document.
Templates allow generic definition of functions and classes. Consider the swap() function below.
// swap() function that works for integers
void swap(int& i, int& j)
{
int temp = i;
= j;
i = temp;
j }
// swap() function that works for floats
void swap(float& i, float& j)
{
float temp = i;
= j;
i = i;
j }
Both of which have the exact same functionality. Wouldn’t it be nice to not have to duplicate functionality.
We can use C++ Templates to write a single swap function. The correct version will be generated by the compiler during compile time.
We can write a templated swap function as follows
template<class T>
void swapValues(T& variable1, T& variable2)
{
;
T temp
= variable1;
temp = variable2;
variable1 = temp;
variable2 }
template<class T>
or
template<typename T>
is called template
prefix.T
is a type parameter.
T
, e.g.,
template<class N>
is valid.T
, L
, M
,
N
, etc.T
can be replaced by any pre-defined of user-defined
C++ typeT
should make sense within the context of the
function body.
T
should support
assignment.template<class T> void showStuff(T stuff1, T stuff2, T stuff3);
template<class T> void showStuff(T stuff1, T stuff2, T stuff3)
{
<< stuff1 << endl
cout << stuff2 << endl
<< stuff3 << endl;
}
<int>(1, 2, 3); showStuff
Compiler generates code for
void showStuff(int, int, int)
at compile time.
<float>(1, 2.4, 3); showStuff
Compiler generates code for
void showStuff(float, float, float)
at compile time.
The following is allowed
template<class N, class M> ...
However, we cannot have unused types
See the following, which defines a Pair class that can store a pair of (the same) items (of type T).
template<class T>
class Pair
{
public:
( );
Pair(T firstValue, T secondValue);
Pairvoid setFirst(T newValue);
void setSecond(T newValue);
( ) const;
T getFirst( ) const;
T getSecondprivate:
;
T first;
T second};
template<class T>
<T>::Pair(T firstValue, T secondValue)
Pair{
= firstValue;
first = secondValue;
second }
template<class T>
void Pair<T>::setFirst(T newValue)
{
= newValue;
first }
template<class T>
<T>::getFirst( ) const
T Pair{
return first;
}
In the above
Pair<T>
Pair
~Pair
We can declare objects as follows:
<int> score; Pair
or
<char> seats; Pair
or
<std::string> first_last_names; Pair
Template types can be uses anywhere standard types can. E.g., see below
int func(const Pair<int>& pair) { ... }
Notice that we have supplied type int
in the above. This
means that function func() only accepts const references of type
Pair<int>
.
If we want function func() to support all Pair<T>
then we need a template function as follows
template<class T>
(const Pair<T>& pair) { ... } T func
Function func() now supports all Pair<T>
.
It is also possible to define new “class type name” for representing specialized class template name as follows
typedef Pair<int> IntegerPair;
And instead of
<int> pair1; Pair
We can write
; IntegerPair pair1
Similarly,
typedef Pair<string> FirstLastName;
, name2, name3; FirstLastName name1
It is common practice to define class type names for sake of code brevity.
It is very common to have friend functions in template classes.
Yes!
std::string
is an alternate name for
std::basic_string<char>
Which means we can do the following
std::basic_string<int>
to design a string
containing integersstd::basic_string<double>
to design a string
containing doublesstd::basic_string<YourOwnClass>
to design a
string containg integersHere’s an example of dynamic memory allocation in template function
template<class T>
* create_array(int n)
T{
* a = new T [n];
Treturn a;
}
int main()
{
double* a;
int n = 5;
= create_array<double>(n);
a return 0;
}
(advanced topic)
The following section is borrowed from “API design for C++” by Martin Reddy, 2011. See also 1.
template <typename T>
class Stack
{
public:
void push(T val);
();
T popbool isEmpty() const;
private:
:vector<T> mStack;
std};
These names are listed after the template keyword in a template declarations. As shown in the example, T is the single template parameter specified in the Stack class.
These are substituted for template parameters during specialization.
In our example, given a specialization Stack
This is when the compiler generates a regular class, method, or function by substituting each of the template’s parameters with a concrete type. This can happen implicitly when we create an object based on a template or explicitly if we want to control when the code generation happens. For instance, the following code creates two specific stack instances and will normally cause the compiler to generate code for these two different types:
<T> myIntStack;
Stack<T> myStringStack; Stack
This is when the compiler decides when to generate code for our template instances. Leaving the decision to the compiler means that it must find an appropriate place to insert the code, and it must also make sure that only one instance of the code exists to avoid duplicate symbol errors. This is non-trivial problem and can cause extra bloat in our object files or longer compile time. Most importantly, implicit instantiation means that we have to include the template definitions in our header files so that the compiler has an access to the definitions whenever it needs to generate the instantiation code.
This is when the programmer determine when the compiler should generate the code for a specific specialization. This can make for much more efficient compilation and link times because the compiler no longer needs to maintain bookkeeping information for all of its implicit instantiations. The onus, however, is then placed on the programmer to ensure that a particular specialization is explicitly instantiated only once. So, explicit instantiation allows us to move the template implementation into the .cpp file, and so hide from the user.
This describes the standard implicit instantation behavior of a C++ compiler wherein it will only generate code for the parts of a template that are actually used. Given the previous two instantiations, for example, if we never called isEmpty() on the myStringStack object, then the compiler would not generate code for the std::string specialization of that method. This means that we can instantiate a template with a type that can be used by some, but not all methods of a class template. If one method uses the >= operator, but the type we want to instantiate does not define this operator. This is fine as long as we don’t call the particular method that attempts to use the >= operator.
When a template is instantiated, the resulting class, method, or function is called a specialization. More specifically, this is an instantiated (or generated) specialization. However, the term specialization can also be used when we provide a custom implementation for a function by specifying concrete types for all the template parameters. The following is called an explicit specialization:
<>
tempatevoid Stack<int>::push(int val)
{
// integer specific push implementation
}
This is when we provide a specialization of the template for a subset of all possible cases. In other words, we specialize one feature of the template but still allow the user to specify other features. For example, if our template accepts multiple parameters, we could partially specialize it by defining a case where we specify a concrete type for only one of the parameters. In our Stack example with a single template parameter, we could partially specialize this template to specifically handle pointers(*) to any type T. This still lets users create a stack of any type, but it also lets us write specific logic to handle the case where users create a stack of pointers. This partially specialized class declaration looks like this:
template <typename T>
class Stack<T*>
{
public:
void push(T* val);
* pop();
Tbool isEmpty() const;
private:
:vector<T*> mStack;
std};
void swap(int& a, int& b)
{
int tmp;
= a;
tmp = b;
a = tmp;
a }
And the template version is
template<class T>
void myswap(T& a, T& b)
{
;
T tmp= a;
tmp = b;
a = tmp;
b }
Or we can use typename
instead of class
template<typename T>
void myswap(T& a, T& b)
{
;
T tmp= a;
tmp = b;
a = tmp;
b }
It is also to mix and match multiple types as follows
template<typename A, typename B, typename C>
void threetypes(A a, B b, C c)
{
// do something here
}
We can use the above template functions in our code as follows:
// fun.cpp
#include <iostream>
#include <string>
using namespace std;
namespace fq
{
template<class T>
void swap(T& a, T& b)
{
;
T tmp= a;
tmp = b;
a = tmp;
b }
}
int main()
{
{
int a = 1;
int b = 2;
<< "a = " << a << endl;
cout << "b = " << b << endl;
cout ::swap<int>(a, b);
fq<< "a = " << a << endl;
cout << "b = " << b << endl;
cout }
{
= "Hello";
string a = "World";
string b << "a = " << a << endl;
cout << "b = " << b << endl;
cout ::swap<string>(a, b);
fq<< "a = " << a << endl;
cout << "b = " << b << endl;
cout }
return 0;
}
In the above code we place swap
within fq
namespace to avoid a conflict with the swap function in the
std
namespace.
Consider the following Pair.h
file
#ifndef __Pair_h__
#define __Pair_h__
#include <iostream>
using namespace std;
template<class T, class U>
class Pair
{
private:
first_;
T second_;
U
public:
();
Pair(T first, U second);
Pairvoid setFirst(T first);
void setSecond(U second);
();
T getFirst();
U getSecondvoid prn();
};
template<class T, class U>
void Pair<T,U>::prn()
{
<< "First = " << first_ << endl;
cout << "Second = " << second_ << endl;
cout }
template <class T, class U>
<T,U>::Pair(T first, U second)
Pair{
first_ = first;
second_ = second;
}
template<class T, class U>
void Pair<T,U>::setFirst(T first)
{
first_ = first;
}
template<class T, class U>
void Pair<T,U>::setSecond(U second)
{
second_ = second;
}
template<class T, class U>
<T,U>::getFirst()
T Pair{
return first_;
}
template<class T, class U>
<T,U>::getSecond()
U Pair{
return second_;
}
#endif
Template class body typically follows the class definition in the header file.
See how we use it below
#include "Pair.h"
#include <string>
typedef Pair<std::string, string> string_string_pair;
int main()
{
<int,char> p(1, 'a');
Pair.prn();
p
<std::string, int> p1("World", 1);
Pair.prn();
p1
("Hello", "World");
string_string_pair p2.prn();
p2
return 0;
}
Notice how we use typedef
above to define
Pair<string,string>
.
The following code sets up a simple array class (see
ArrT.h
).
#ifndef __ArrT_h__
#define __ArrT_h__
#include <iostream>
using namespace std;
template<class T>
class ArrT
{
private:
* mem_;
Tint n_;
public:
(int n);
ArrT~ArrT();
& operator[](int i);
Tconst T& operator[](int i) const;
void prn();
int size() const { return n_; }
friend ostream& operator<<(ostream& o, const ArrT<T>& a)
{
for (int i=0; i<a.size(); ++i) {
<< a[i] << endl;
o }
return o;
}
};
template<class T>
<T>::ArrT(int n) : n_(n)
ArrT{
mem_ = new T[n];
}
template<class T>
<T>::~ArrT()
ArrT{
delete[] mem_;
}
template<class T>
& ArrT<T>::operator[](int i)
T{
return mem_[i];
}
template<class T>
const T& ArrT<T>::operator[](int i) const
{
return mem_[i];
}
template<class T>
void ArrT<T>::prn()
{
for (int i=0; i<n_; ++i)
{
<< mem_[i] << endl;
cout }
}
#endif
And the corresponding main
file is:
#include <iostream>
#include <string>
using namespace std;
#include "ArrT.h"
int main()
{
ArrT<char> ac(3);
ac[1] = '1';
ac.prn();
ArrT<float> af(3);
af[1] = 34.4;
af.prn();
cout << af << endl;
ArrT<string> as(3);
as[1] = "Marty";
as.prn();
cout << as << endl;
return 0;
}
Lets again consider our Pair
class
#ifndef __Pair_h__
#define __Pair_h__
#include <iostream>
using namespace std;
template<class T, class U>
class Pair
{
private:
first_;
T second_;
U
public:
();
Pair(T first, U second);
Pairvoid setFirst(T first);
void setSecond(U second);
();
T getFirst();
U getSecondvoid prn();
};
template<class T, class U>
void Pair<T,U>::prn()
{
<< "First = " << first_ << endl;
cout << "Second = " << second_ << endl;
cout }
template <class T, class U>
<T,U>::Pair(T first, U second)
Pair{
first_ = first;
second_ = second;
}
template<class T, class U>
void Pair<T,U>::setFirst(T first)
{
first_ = first;
}
template<class T, class U>
void Pair<T,U>::setSecond(U second)
{
second_ = second;
}
template<class T, class U>
<T,U>::getFirst()
T Pair{
return first_;
}
template<class T, class U>
<T,U>::getSecond()
U Pair{
return second_;
}
#endif
If we use it as follows:
// pair_main.cpp
#include "Pair.h"
#include <string>
#include <vector>
typedef Pair<std::string, std::string> string_string_pair;
int main()
{
<int,char> p(1, 'a');
Pair.prn();
p
<std::string, int> p1("World", 1);
Pair.prn();
p1
("Hello", "World");
string_string_pair p2.prn();
p2
std::vector<int> v;
.push_back(1);
v<int, std::vector<int> > p3(1, v); // Problematic statement!
Pair.prn();
p3
return 0;
}
The compiler will complain. That’s because
std::vector<int>
conflicts with the
cout << "Second = " << second_ << endl;
statement in the following function.
template<class T, class U>
void Pair<T,U>::prn()
{
<< "First = " << first_ << endl;
cout << "Second = " << second_ << endl;
cout }
What this means that only those template types are allowed that “support” all the functionality that is asked of them in the template body.
The following code will give a compile error since class
A
doesn’t support +
operation that is used in
the addition
function.
#include <iostream>
using namespace std;
template<class T>
(const T& a, const T& b)
T addition{
= a + b;
T c
return c;
}
class A
{
private:
int v_;
public:
() : v_(0) {}
A(int v) : v_(v) {}
Aint& value() { return v_; }
const int& value() const { return v_; }
};
int main()
{
int a = 1;
int b = 2;
int c = addition<int>(a, b);
<< "c = " << c << endl;
cout
;
A a1(3);
A a2<< addition<A>(a1, a2).value() << endl;
cout
return 0;
}
A
to add a +
operator.We can fix it by providing a +
operator for class
A
.
#include <iostream>
using namespace std;
template<class T>
(const T& a, const T& b)
T addition{
= a + b;
T c
return c;
}
class A
{
private:
int v_;
public:
() : v_(0) {}
A(int v) : v_(v) {}
Aint& value() { return v_; }
const int& value() const { return v_; }
// See here
friend A operator+(A a, A b)
{
(a.value() + b.value());
A creturn c;
}
};
int main()
{
int a = 1;
int b = 2;
int c = addition<int>(a, b);
<< "c = " << c << endl;
cout
;
A a1(3);
A a2<< addition<A>(a1, a2).value() << endl;
cout
return 0;
}
A
differently.Or we can create a specialization for addition
method
that handles class A
differently. See below for more about
specializations.
#include <iostream>
using namespace std;
template<class T>
(const T& a, const T& b)
T addition{
= a + b;
T c
return c;
}
// And this is a specialization for addition() for class A.
// Notice how it doesn't need class A to have a + operator.
template<>
(const A& a, const A& b)
A addition{
return A(a.value() + b.value());
}
class A
{
private:
int v_;
public:
() : v_(0) {}
A(int v) : v_(v) {}
Aint& value() { return v_; }
const int& value() const { return v_; }
};
int main()
{
int a = 1;
int b = 2;
int c = addition<int>(a, b);
<< "c = " << c << endl;
cout
;
A a1(3);
A a2<< addition<A>(a1, a2).value() << endl;
cout
return 0;
}
Taken from http://en.cppreference.com/w/cpp/language/template_specialization
Any of the following can be fully specialized:
Consider the code below
// spec.cpp
#include <iostream>
template<typename T> // primary template
struct is_void : std::false_type
{
};
template<> // explicit specialization for T = void
struct is_void<void> : std::true_type
{
};
int main()
{
// for any type T other than void, the
// class is derived from false_type
std::cout << is_void<char>::value << '\n';
// but when T is void, the class is derived
// from true_type
std::cout << is_void<void>::value << '\n';
}
The following source code showcases the how we are able to specialize a template function.
#include <iostream>
#include <string>
using namespace std;
namespace fq
{
template<class T>
void swap(T& a, T& b)
{
;
T tmp= a;
tmp = b;
a = tmp;
b }
// Below we specialize the swap method for
// std::string type
template<>
void swap(std::string& a, std::string& b)
{
std::string tmp;
= a;
tmp = b;
a = tmp;
b << "Specialized swap called." << endl;
cout }
}
int main()
{
{
int a = 1;
int b = 2;
<< "a = " << a << endl;
cout << "b = " << b << endl;
cout ::swap<int>(a, b);
fq<< "a = " << a << endl;
cout << "b = " << b << endl;
cout }
{
= "Hello";
string a = "World";
string b << "a = " << a << endl;
cout << "b = " << b << endl;
cout ::swap<string>(a, b);
fq<< "a = " << a << endl;
cout << "b = " << b << endl;
cout }
return 0;
}