Overview
C++ provides two major mechanisms for code reuse: inheritance and templates. Templates can become complex so we are going to use function templates as a gentle introduction. Generally speaking, a function template applies the same algorithm to different data types.
Preview
- Templates are a core language feature for code reuse.
- Templates functions vary the argument type while using the same code.
- Write several concrete versions of your function and debug them before trying to create a template function.
- Do not provide both template functions and standard functions with the same name - it is confusing.
- Template specialization allows you to provide different implementations for different types.
Getting Started
With any kind of templates, it is best practice to write multiple concrete examples before creating the template.After creating these concrete examples, you look at what is common between them and what differs.
Concrete Functions
We are going to create a function that writes both the data type and the value to standard output. We will call the function logger(). Let's start with a version of logger() that works with int .
#include <stdio> #include <typeinfo> void logger(int x) { std::cout << typeid(x).name() << "=" << x << "\n"; }
We now add a version that works for double :
void logger(double x) { std::cout << typeid(x).name() << "=" << x << "\n"; }
Calling the Concrete Functions
Let's call these logger() functions and see what the output looks like. Here is our main() :
int main(int argc, char* argv[]) { int status = 0; int life = 42; logger(life); double pi = 3.1415; logger(pi); //... }
Our output is:
int=42 double=3.1415
Create the Template Function
Looking at the concrete logger() versions for type int and type double , we can see a pattern. Ironically, it is best to follow a pattern when making a function template:
template <typename T> void logger(T t) { std::cout << typeid(t).name() << "=" << t << "\n"; }
Keywords
First, notice the line template <typename T> that is on a line by itself. This is not required by the language but is a common practice to make templates more readable. The keyword template alerts the compiler that this is a template and then the angle brackets hold the type (or types) that are generic.
typename T specifies the type that will be varied. This is called the parameterizing type. Note that you may also see this specified as class T , which before C++03, was the only way. While class is still acceptable, I prefer the keyword typename as it has a single meaning in C++, unlike class .
Calling the Template Functions
We erase the concrete definitions of logger() for types int and double . Do we have to make any changes to the calling code? Well, we could by using angle brackets after the template function name to explicitly specify the parameterizing type like this:
int life = 42; logger<int>(life); double pi = 3.1415; logger<double>(pi);
However, for function templates (and not for class templates), the compiler can perform what is called type deduction, which means that the compiler determines the parameterizing types on its own when unambiguous. In this case, we can drop the parameterizing type with the angle brackets.
int life = 42; logger<>(life); double pi = 3.1415; logger<>(pi);
Keeping the angle brackets tell the compiler that this is a parameterized template function but leaves it up to the compiler to determine the parameterizing type. In well written programs, you do not even need the angle brackets:
int life = 42; logger(life); double pi = 3.1415; logger(pi);
Why did I say, "In a well written program"? Well, it is a bad idea to include an overloaded function with the same name as a template function. This is because function templates do not participate in function overloading. Having a template function with the same name as a function or overloaded function is a recipe for confusion.
However the template function gets its parameterizing type(s), you say that the template function was specialized by providing the type. Templates only generate code when they are specialized. If a template is never specialized, no code for that template is ever generated.
Explicit Specialization
You can also perform what is called explicit specialization by providing the definition for a parameterized template function. This allows you to alter how the template function behaves for specific classes.
What if we wanted to use logger() on a std::string ?
int life = 42; logger(life); double pi = 3.1415; logger(pi); std::string str = "Template functions"; logger(str);
The output is not what we expect:
int=42 double=3.1415 class std::basic_string<char,struct std::char_traits<char>,class std::allocator< char> >=Template functions
Your output may differ somewhat. It depends on how std::string is defined for your C++ compiler. In any case, it is not the std::string=Template functions that we expected.
How can we handle this? We can tell the template function to use different code for std::string by specializing the template function on std::string . After the template is declare, we provide a specialization for std::string :
template <typename T> void logger(T x) { std::cout << typeid(x).name() << "=" << x << "\n"; } template<> void logger<>(std::string str) { std::cout << "std::string" << "=" << str << "\n"; }
Note that the angle brackets after the template keyword are empty. Also note the added empty angle brackets after the template function name. Finally, note that we pass a concrete class in the argument.
Why Specialize?
By specializing a template function, we can cause different behavior for different types. There are often good reasons for this, like handling pointers differently than value types. Make sure not to create surprises for clients of your code: the specialization should be in the spirit of the template function.
Summary
- Template functions are a mechanism of code reuse.
- Write several concrete versions of your function before trying to templatize it.
- Do not also provide non-template versions of the function as this causes confusion.
- Specialization allows you to provide different implementations for different types.