Introduction
For me, templates help reducing hard-codes and make our codes more scalable. Template functions could accept and return any types of data. The specific data types are not specified in function prototypes.
Without templates, codes could be messy, have lots of logics, and hard to debug. For example:
#include <iostream>
#include <string>
using namespace std;
typedef struct Car{ string name = "Car"; } Car;
typedef struct Bike{ string name = "Bike"; } Bus;
typedef struct Yatch{ string name = "Yatch"; } Yatch;
bool isCar( string name){
if (name == "Car")
return true;
else
return false;
}
bool isBike( string name){
if (name == "Bike")
return true;
else
return false;
}
bool isYatch( string name){
if (name == "Train")
return true;
else
return false;
}
int main()
{
Car myCar;
if ( isCar(myCar.name) && !isBike(myCar.name) && !isYatch(myCar.name) )
cout<<" my vehicle is "<<myCar.name<<endl;
else if ( !isCar(myCar.name) && isBike(myCar.name) && !isYatch(myCar.name) )
cout<<" my vehicle is "<<myCar.name<<endl;
else if ( !isCar(myCar.name) && !isBike(myCar.name) && isYatch(myCar.name) )
cout<<" my vehicle is "<<myCar.name<<endl;
return 0;
}
Let say we have 3 different vehicle types with three different logics to implement. The above codes requires a bunch of if-elses. Template functions could help. In addition, I want to scale up the code such that I could write a single template function that takes as many data type as I want. ( Let say I intend to collect all kinds of vehicles).
So in this blog, I demonstrate the use of template functions with folding expression and then with variadic template recursive method. The first method is easier to implement but is only available with C++17 and up.
Using Folding Expression
The above program could be simplified using template function and fold expression:
#include <iostream>
#include <string>
using namespace std;
typedef struct Car{ string name = "Car"; } Car;
typedef struct Bike{ string name = "Bike"; } Bus;
typedef struct Yatch{ string name = "Yatch"; } Yatch;
template<typename T>
string getVehicleName(const T& vehicle){
return vehicle.name;
}
template<typename ...T>
void getVehicleCollection(const T& ... vehicle ){
string output = "my vehicle is ";
((output += getVehicleName(vehicle) + " and "), ...);
// Trim off the last " and " (length is 5 characters)
if (!output.empty()) {
output = output.substr(0, output.length() - 5);
}
cout<<output<<endl;
}
int main()
{
Car myCar;
Bike myBike;
getVehicleCollection<Car, Bus>(myCar, myBike);
return 0;
}
//output
// my vehicle is Car and Bike
The above program eliminates the needs for 3 isCar
, isBike
, isYatch
. Instead a single function getVehicleName
could provide logic for all three functions.
getVehicleName
itself is template function, which could accepts any type and performs logic according to the type. Thus it is more powerful, scalable. Instead of providing one or more common attributes (in this case a string name) to isCar, isBike, isYatch and implementing certain logic depending on the attributes' values, we now can pass in any type and have all logics implemented in 1 function!!!
It can also be invoked any number of time, depending on the number of type arguments provided to getVehicleCollection
.
How template and fold expression work:
- Compiler interprets and expands function template at compile time. Thus:
((output += getVehicleName(vehicle) + " and "), ...)
is interpreted as
getVehicleName(myCar) + " and " + getVehicleName(myBike) + " and "
It happens because the compiler "reads" the code at compile time and know that myCar and myBike are provided to the function getVehicleCollection
.
C++ variadic template recursive
The following implements the same program above using variadic template recursive method.
#include <iostream>
#include <string>
using namespace std;
typedef struct Car { string name = "Car"; } Car;
typedef struct Bike { string name = "Bike"; } Bus;
typedef struct Yatch { string name = "Yacht"; } Yatch;
string output = "my vehicle is ";
template <typename T>
string getVehicleName(const T& vehicle) {
return vehicle.name;
}
// Base case for recursive template
template <typename Tail>
void getVehicleCollection(const Tail& vehicle, string & output) {
output += getVehicleName(vehicle);
}
// Recursive template for multiple arguments
template <typename Head, typename ... Tail>
void getVehicleCollection(const Head& first, const Tail&... others, string & output) {
output += getVehicleName(first) + " and ";
getVehicleCollection<Tail ...> (others ..., output);
}
int main() {
Car myCar;
Bike myBike;
Yatch myYatch;
getVehicleCollection<Car, Bike, Yatch>(myCar, myBike, myYatch, output);
cout << output << endl;
return 0;
}
// output
// my vehicle is Car and Bike and Yacht
The method is recursive because instead of using fold expression, recursive calls to template will perform the task. We need:
- template for general case: implements the general logics and makes recursive calls to itself. Note the syntax:
- the function define a single type to start with. We call it
Head
. We will define all other following types asTail
.
- the function define a single type to start with. We call it
// function declaration:
template <typename Head, typename ... Tail>
void getVehicleCollection(const Head& first, const Tail&... others, string & output)
- The recursive calls will only include the remaining types. Thus we call provide
<Tail ...>
// recursive calls needs to include the Tails.
getVehicleCollection<Tail ...> (others ..., output);
- template for base case: since the recursive call will expand the function with each of the provided types, in the end, we will arrive the base case where we have only 1 type. Thus, the base case is a template function that has only 1 type.
Concepts
- template variadic functions are those that can take any number of parameters of any type.
- The general declaration is:
template(typename arg, typename... args)
return_type function_name(arg T1, args... T2)
- The syntax for function call (it is also called temple specialization) is like below.
function_name<type1, type 2, type3> (type1 var1, type2 var2, type 3 var2)
- A template parameter pack is a template parameter that accepts zero or more template arguments (non-types, types, or templates). A function parameter pack is a function parameter that accepts zero or more function arguments. The following is the syntax of template parameter pack
<typename ... T> // T will be expanded
// and
(args ... T2>
// T2 could be then expanded to any the list of types provided and known at compile time.
- Parameter pack expansion would expand the parameter pack into a list of zero or more comma-separated instantiations of the pattern, where the name of the parameter pack is replaced by each of the elements from the pack.
- A function parameter pack is a trailing function parameter pack if it is the last function parameter of a function template. Source: IBM.com
Reference
- for more information on other kinds of templates such as class templates: https://www.linkedin.com/pulse/introduction-c-templates-marcus-tomlinson-/
- https://kubasejdak.com/techniques-of-variadic-templates