Authors: Michał Przewoźniczek, Marcin Jodłowiec, Marcin Komarnicki i Artur Zawisza
Effective Programming Techniques – task 4
Template classes
Templates allow you to reuse the same code multiple times. This is convenient when
you create functions and classes that are to be used, for example, to store some values. A
typical and often used class template is vector. This class can be used to store any types, but
the stored type must be specified when creating the vector. It is important that the logic of
operations performed by vector is not dependent on the stored type. It can therefore be written
without knowing the stored type.
In C++, in addition to class templates, you can also declare function templates, but in
this exercise we will focus only on classes. An example template that creates an array of type
T is shown below.
Template declaration, type T is a necessary parameter. Its definition is required when our
program wants to use the CTable class.
template< typename T > class CTable
{
public:
CTable() { i_size = 0; pt_table = NULL; }
~CTable() { if (pt_table != NULL) delete [] pt_table; }
bool bSetLength(int iNewSize);
T* ptGetElement(int iOffset);
bool bSetElement(int iOffset, T tVal);
private:
int i_size;
T *pt_table;
};//template< typename T > class CTable
In the case of template types in C++, due to linker error visibility, method implementations
are usually placed in header files below the class declaration. This is because the compiler
needs to know how to generate a given function for a specific type T. In the case of the CTable
class, the method definitions will be as follows.
„ZPR PWr – Zintegrowany Program Rozwoju Politechniki Wrocławskiej”
2/7
template <typename T>
bool CTable<T>::bSetLength(int iNewSize)
{
if (iNewSize <= 0) return(false);
T *pt_new_table;
pt_new_table = new T[iNewSize];
if (pt_table != NULL)
{
int i_min_len;
if (iNewSize < i_size)
i_min_len = iNewSize;
else
i_min_len = i_size;
for (int ii = 0; ii < i_min_len; ii++)
pt_new_table[ii] = pt_table[ii];
delete[] pt_table;
}//if (pt_table != NULL)
pt_table = pt_new_table;
return(true);
}//bool CTable<T>::bSetLength(int iNewSize)
template <typename T>
T* CTable<T>::ptGetElement(int iOffset)
{
if ((0 <= iOffset) && (iOffset < i_size)) return(NULL);
return(&(pt_table[iOffset]));
}//T* CTable<T>::ptGetElement(int iOffset)
template <typename T>
bool CTable<T>::bSetElement(int iOffset, T tVal)
{
if ((0 <= iOffset) && (iOffset < i_size)) return(false);
pt_table[iOffset] = tVal;
return(true);
}//bool CTable<T>::bSetElement(int iOffset, T tVal)
Please note that the behavior of the CTable class is the same whether the type of T is int,
double, class, or pointer (single or multiple).
„ZPR PWr – Zintegrowany Program Rozwoju Politechniki Wrocławskiej”
3/7
The use of the CTable class can be as follows.
int i_template_test()
{
CTable<int> c_tab_int;
CTable<double *> c_tab_double_point;
c_tab_int.bSetLength(10);
c_tab_int.bSetElement(1, 22);
int i_val = *(c_tab_int.ptGetElement(1));
double d_my_doub = 5;
c_tab_double_point.bSetLength(2);
c_tab_double_point.bSetElement(1, &d_my_doub);
**(c_tab_double_point.ptGetElement(1)) = 5;
}//int i_template_test()
Creating methods dedicated to a given template type
Sometimes we would like some template types to behave differently, depending on the type of
the parameter. Method specialization is achieved in the following way. We add the
sGetKnownType method to the CTable class.
template< typename T > class CTable
{
public:
CTable() { i_size = 0; pt_table = NULL; }
~CTable() { if (pt_table != NULL) delete [] pt_table; }
bool bSetLength(int iNewSize);
T* ptGetElement(int iOffset);
bool bSetElement(int iOffset, T tVal);
CString sGetKnownType();
private:
int i_size;
T *pt_table;
};//template< typename T > class CTable
The implementation of the sGetKnownType method looks as follows.
template <typename T>
CString CTable<T>::sGetKnownType()
{
CString s_type = "Unknown";
return(s_type);
}//CString CTable<T>::sGetKnownType()
template <>
CString CTable<int>::sGetKnownType()
{
CString s_type = "INT";
return(s_type);
}//CString CTable<int>::sGetKnownType()
„ZPR PWr – Zintegrowany Program Rozwoju Politechniki Wrocławskiej”
4/7
Example of use:
int i_template_test()
{
CTable<int> c_tab_int;
CTable<double *> c_tab_double_point;
c_tab_int.bSetLength(10);
c_tab_int.bSetElement(1, 22);
int i_val = *(c_tab_int.ptGetElement(1));
double d_my_doub = 5;
c_tab_double_point.bSetLength(2);
c_tab_double_point.bSetElement(1, &d_my_doub);
**(c_tab_double_point.ptGetElement(1)) = 5;
CString s_type;
s_type = c_tab_int.sGetKnownType();
s_type = c_tab_double_point.sGetKnownType();
}//int i_template_test()
If the sGetKnownType method is used as above, then for any template type other than int,
sGetKnownType will return "Unknown", and for int it will return "INT". There can be as many
specializations as you want.
Compiler behavior when encountering template classes.
A class template should be treated as a recipe for making a class. For example, when
compiling the above program, the compiler will determine that CTable will be used with two
types - int and double*. Based on the template definition, the compiler will create two
independent implementations - template classes. In the first one, the int type will be used
instead of T, and in the second one, the double* type will be used.
„ZPR PWr – Zintegrowany Program Rozwoju Politechniki Wrocławskiej”
5/7
Task
NOTES:
1. When writing your own program, you may use a different naming convention
than that presented in the task description and examples. However, you should
adhere to some consistent coding conventions in accordance with the course
requirements.
2. You are not allowed to use exceptions in any form (it is only a reminder).
3. You are supposed to use C++98 standard.
Program CResult template, which stores a result of a method or errors when the execution of
the method fails. Perform the following exercises.
1. Program CError class, which stores at least a description of the error.
2. Implement CResult template according to the following specification. Note that it is
only the minimum requirement. You can write any auxiliary methods.
template <typename T, typename E>
class CResult
{
public:
CResult(const T& cValue);
CResult(E* pcError);
CResult(vector<E*>& vErrors);
CResult(const CResult<T, E>& cOther);
~CResult();
static CResult<T, E> cOk(const T& cValue);
static CResult<T, E> cFail(E* pcError);
static CResult<T, E> cFail(vector<E*>& vErrors);
CResult<T, E>& operator=(const CResult<T, E>& cOther);
bool bIsSuccess();
T cGetValue();
vector<E*>& vGetErrors();
private:
T *pc_value;
vector<E*> v_errors;
};
In the above example, T is a return type, whereas E is an error type. Constructors
CResult(const T& cValue) and CResult(E* pcError) allow of the implementation of
the following piece of code:
CResult<double, CError> eDivide(double dDividend, double dDivisor)
{
if (dDivisor == 0)
{
return new CError("cannot divide by zero");
}
„ZPR PWr – Zintegrowany Program Rozwoju Politechniki Wrocławskiej”
6/7
return dDividend / dDivisor;
}
in which, we can return in a convenient way new CError("cannot divide by zero") lub
dDividend / dDivisor or dDividend / dDivisor directly as the method result. We do
not have to wrap them in an object of CResult<double, CError> class.
When E is a type which name does not suggest that we return an error
const int iERROR_CODE_DIVISION_BY_ZERO = 123;
CResult<double, int> eDivide(double dDividend, double dDivisor)
{
if (dDivisor == 0)
{
return new int(iERROR_CODE_DIVISION_BY_ZERO);
}
return dDividend / dDivisor;
}
it can lead to a situation were it is hard to distinguish if we return a successful result or
an error. In order to avoid such ambiguity you should implement static methods
including static CResult<T, E> cOk(const T& cValue) and static CResult<T, E>
cFail(E* pcError). They allow of the implementation of the following piece of code.
CResult<double, CError> eDivide(double dDividend, double dDivisor)
{
if (dDivisor == 0)
{
return CResult<double, CError>::cFail(new CError("cannot divide by
zero"));
}
return CResult<double, CError>::cOk(dDividend / dDivisor);
}
We can assume that class T has a correctly implemented copy constructor. A single
object of CResult<T, E> template can store many pointers to objects of E class. These
pointers allow to store error objects of different classes as long as their base class is E
class.
3. When we want to use CResult<T, E> as a return type for a method that should not
return anything, an intuitive choice seems to be CResult<void, E> type. In the current
implementation, the creation of an object of CResult<void, E> template is impossible.
For example, CResult(const void& cValue) is not allowed. Program full template
specialization of CResult<T, E> template according to the following specification.
Note that it is only the minimum requirement that CResult<void, E> template has to
follow. You can write any auxiliary methods.
„ZPR PWr – Zintegrowany Program Rozwoju Politechniki Wrocławskiej”
7/7
template <typename E>
class CResult<void, E>
{
public:
CResult();
CResult(E *pcError);
CResult(vector<E*>& vErrors);
CResult(const CResult<void, E>& cOther);
~CResult();
static CResult<void, E> cOk();
static CResult<void, E> cFail(E* pcError);
static CResult<void, E> cFail(vector<E*>& vErrors);
CResult<void, E>& operator=(const CResult<void, E>& cOther);
bool bIsSuccess();
vector<E*>& vGetErrors();
private:
vector<E*> v_errors;
};
4. Use objects of CResult<T, E> template to try to create a tree, from the previous task
list, from the given formula. In the case of errors in the tree creation, you do not have
to return any tree.
5. Implement a class that saves any object of CResult<T, CError> template to a file. For
any T different than CTree*, you should save only error descriptions (if any).
Otherwise, it is necessary to save error descriptions or the tree in the prefix notation.
Zalecana literatura
Bjarne Stroustrup “The C++ Programming Language”, Addison-Wesley Professional, Year:
2013
The lecture
Any source find online solve this task in c++