c++ : 1 – basics of templates

As I learned from a free udemy course1. Code

  1. Avoiding redefining a function for different data types:
#include<iostream>
using namespace std;

template <typename T>
T Max(T x, T y)
{
    return x > y ? x : y;
}

int main()
{
    cout<<Max(3,25)<<endl;
    cout<<Max(3.24,25.13)<<endl;
}

T is the placeholder for int, double,float… and custom class objects. The type of T is deduced from argument(s)(argument deduction). The catch is, type is deduced from the first argument and following arguments are treated as same type. We can override the deduction for a particular type(template specialisation)

Compiler will generate(template instantiation) separate code for each type. Compiler do this instantiation in the following circumstances:

  • A function template is called.
  • Taking address of the function template. int (*pfn)(int,int) = Max
  • Using explicit instantiation. template char Max(char x, char y)
  • Creating explicit specialisation. See an example below:
#include<iostream>
#include <cstring>
using namespace std;

template <typename T>
T Max(T x, T y)
{
    return x > y ? x : y;
}
//Optional specification of type
//template <> const char* Max<const char*>
(const char* x,const char* y)
template <> const char* Max(const char* x,const char* y)
{
    return strcmp(x,y)>0 ? x : y;
}

int main()
{
    cout<<Max(3,25)<<endl;
    const char * a {"A"};
    const char * b {"B"};
    cout<<Max(a,b)<<endl;
}

Non-type template arguments

The usual argument is in a template is T (template <typename T> ). However, we can provide non-type template arguments like int, string, object, etc . Example (and also2) :

#include<iostream>
using namespace std;

// Function to calculate sum of an array
template <typename T, int size>
T Sum(T (&parr)[size])
{
    T s{};//initialises to zero
    for (int i = 0; i < size; i++) {
        s += parr[i];
    }
    return s;
}
int main ()
{
    int a[] = {2,6,7,3};
    cout<<"Sum = "<<Sum(a)<<endl;
}

non-type template arguments should be constant expressions and compiler should be able to calculate its value at compile time. The implementation of std::begin and std::end are defined in this way.

Variadic Templates

We can make template accept any number of arguments using initialiser list as follows:

#include<iostream>
using namespace std;

template <typename T>
void print(initializer_list<T> args)
{
    for (const auto &x:args) {
        cout<<x<<" ";
    }
}
int main ()
{
    print({1,2,3,4});
    return 0;
}

But if we change 2 to 2,.1, it results i an error “deduced conflicting types for parameter ‘_Tp’ (‘int’ and ‘double’)
12 | print({1,2.1,3,4});”
This is because it can take only one particular data type. Here we use variadic templates.

#include<iostream>
using namespace std;
//base function for exiting the loop
//when args is 0, uses this function
void Print()
{
    cout<<endl;
};
//template parameter pack
template <typename T, typename...Params>
//Function parameter pack
void Print(const T& a, const Params&... args)
{
    cout<<a;
    if(sizeof...(args)>0){
        cout<<", ";
    }
    Print(args...);//recursive call
}

int main ()
{
    Print(1,2.1,3,4);
    return 0;
}

Class templates

#include<iostream>
using namespace std;

template <typename T>
class Stack
{
    T m_Buffer[512];
    int m_Top{-1};
  public:
  //Default constructor
  Stack() = default;
  //Copy constructor
  Stack(const Stack<T>& s){
    m_Top = s.m_Top;
    for(int i=0;i<=m_Top;++i){
      m_Buffer[i] = s.m_Buffer[i];
    }
  }
  //factory metho for making Stack
  static Stack Create();    
  void Push(const T& elem){
     m_Buffer[++m_Top] = elem;
  }
  const T& Top(){
    return m_Buffer[m_Top];
  }
  void Pop();
  bool isEmpty();
};//end of Stack

template<typename T>
void Stack<T>::Pop(){
    --m_Top;
}

template<typename T>
bool Stack<T>::isEmpty(){
    return m_Top==-1;
}

template <typename T>
Stack<T> Stack<T>::Create(){
   return Stack<T>(); 
} 

int main ()
{
  Stack<float>a = Stack<float>::Create();
  cout<<a.isEmpty()<<endl;
  Stack<int> s;
  s.Push(-5);s.Push(7);s.Push(9);
  s.Push(-1);s.Push(0);
  //using copy constructor
  auto s2(s);
  while( !s2.isEmpty()){
      cout<<s2.Top()<<endl;
      s2.Pop();
  }
  return 0;
}

Explicit specialisation for class template

#include<iostream>
using namespace std;

template<typename T>
class prettyPrinter{
  T * data_;//pointer
  public:
  //constructor with initialiser
  prettyPrinter(T *data):data_(data){}
  //printing the value by dereferencing
  void print(){
      cout<<"{ "<<*data_<<" }"<<endl;
  }
  T * getData(){
      return data_;//return the pointer
  }    
};
//explicit specialisation for char
template<>
class prettyPrinter<char*>{
  char * data_;//pointer
  public:
  prettyPrinter(char *data):data_(data){}
  void print(){
      cout<<"{ "<<data_<<" }"<<endl;
  }
  char * getData(){
      return data_;
  }    
};

int main()
{
  int data_int = 5;
  double data_d = 5.001;
  prettyPrinter<int> pi(&data_int);
  prettyPrinter<double> pd(&data_d);
  pi.print();
  pd.print();
  //error hence call for specialisation
  /* char *p {"Hello"};
  prettyPrinter<char *> ps(p);
  ps.print(); */
  char *p {"Hello"};
  prettyPrinter<char *> ps(p);
  ps.print();

  return(0);
}

Specialising only a member function of the class

#include<iostream>
#include<vector>
using namespace std;

template<typename T>
class prettyPrinter{
    T * data_;//pointer
    public:
    //constructor with initialiser
    prettyPrinter(T *data):data_(data){}
    //printing the value by dereferencing
    void print(){
        cout<<"{ "<<*data_<<" }"<<endl;
    }
    T * getData(){
        return data_;//return the pointer
    }    
};
//only print function needs to be modified
//This has to be outside the class
template<>
void prettyPrinter<vector<int>>::print(){
    cout<<"{ ";
    for (const auto & x: *data_){
        cout<<x;
    }
    cout<<"}";
}

int main()
{
    vector<int> v {1,2,3,4,5};
    prettyPrinter<vector<int>> pv(&v);
    pv.print();
    return(0);
}

Partial specialisation

Specifying value for one of the argument.

#include<iostream>
#include<vector>

template<typename T, int columns>
class prettyPrinter{
    T * data_;
    public:
    prettyPrinter(T *data):data_(data){}
    void print(){
        cout<<"Columns = "<<columns<<endl;
        cout<<"{ "<<*data_<<" }"<<endl;
    }
    T * getData(){
        return data_;
    }    
};
//partial specialisation
template<typename T>
class prettyPrinter<T,80>{
    T * data_;
    public:
    prettyPrinter(T *data):data_(data){}
    void print(){
        cout<<"Using 80 columns : "<<endl;
        cout<<"{ "<<*data_<<" }"<<endl;
    }
    T * getData(){
        return data_;
    }
};

int main()
{
    int data = 800;
    prettyPrinter<int,40> p(&data);
    p.print();
    //partially specified version is taken
    prettyPrinter<int,80> p1(&data);
    p1.print();
    return(0);
}

typedef(Type Definition)

  • Defining a nick name for existing type. Does not introduce a new type.
  • Useful to construct a shorter and more meaningful name, hence improve readability.

Examples:

  • typedef unsigned integer unit; uint val{}
  • typedef long long LLONG; LLONG val{}
  • typedef std::vector<std::list<Employee>> Teams; Teams testingTeams
  • typedef const char *(*errFn)(int); errFn pfn = getErrorMessage

Type Aliases(same as typedef, introduced in c++11)

  • using unit = unsigned integer; uint val{}
  • using LLONG = long long; LLONG val{}
  • using Teams = std::vector<std::list<Employee>>; Teams testingTeams
  • using errFn = const char *(*)(int); errFn pfn = getErrorMessage
#include<iostream>
#include<vector>
#include<list>
using namespace std;

const char *getErrorMessage(int errNo){
    return "A message !!";
}
typedef const char *(*PFN1)(int);
using PFN2 = const char *(*)(int);
void showError (PFN1 p){
    cout<<p<<endl;
}
typedef vector<list<string>> Names;
using Namez = vector<list<string>>;
template <typename T>
//this is not possible by typedef
using NamezT = vector<list<T>>;
int main()
{
    PFN1 p = getErrorMessage;
    showError(p);
    Names names;
    Namez namez;
    NamezT<string> sNames;
    return(0);
}

Source

  1. free udemy coursehttps://www.udemy.com/course/beg-cpp-temp/
  2. GeekForGeeks:template-non-type-arguments

Leave a Comment

Your email address will not be published.