In generic programming, constructs are written in terms of to-be-specified-later types that are then instantiated when needed for
specific types provided as parameters.
We are writing instructions to produce code not code directly.
We can have instantiations associated with multiple different types, but we just have the one abstract representation of the construct, a blueprint.
We will look at functions first, then classes.
These are not the same and it’s easy to get them mixed up.
The function templates are the prototypes expressed in terms of the to-be-specified-later types.
The template functions are instantiations of those templates.
We write function templates, and the compiler produces template functions.
C++ functions can have default arguments, so that if an argument isn’t provided we have a value to use.
From C++11 on it’s possible to provide default arguments for function templates, and for class templates too.
Example:
It uses a second type parameter, F, to specify the type of a callable object and a function parameter f bound to a callable object.
The default is a library function-object template less.
#include <iostream>
#include <functional>
using namespace std;
template <typename T, typename F=less<T>>
int compare(const T &v1, const T &v2, F f=F())
{
if (f(v1, v2)) return -1;
if (f(v2, v1)) return 1;
return 0;
}
int main()
{
cout << compare(10,0) << endl;
cout << compare(0,10) << end;
cout << compare(10,10) << endl;
return 0;
}
Non-type template parameters represent values rather than types, and instantiation results in a version of a template with a specific fixed value.
Those values must be constant expressions, which the compiler can evaluate at compile time.
Example:
template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M])
{
return strcmp(p1, p2);
}
compare("hi", "mum"); // produces: int compare(const char (&p1)[3], const char (&p2)[4])
There are a few reasons why it can be helpful to do compile time calculations:
Efficiency: Pre-calculation of a value, often a size.
Type-safety: Computing a type at compile time.
Simplify concurrency: You can’t have a race condition on a constant.
Why not just fix them and calculate at programming time?
They aren’t necessarily the same for each compilation.
We might have a large table of values that we are going to need to use over and over once a program is running, but explicitly entering the values could use up a lot of space.
#include <iostream>
using namespace std;
constexpr int factorial(int n)
{
return (n ? (n * factorial(n - 1)) : 1);
}
constexpr int f10 = factorial(10);
int main()
{
cout << f10 << endl;
return 0;
}
Based on https://rosettacode.org/wiki/Compile-time_calculation
The constexpr is C++’s way of requesting that we evaluate something at compile time.Here goes a more useful version of this.
It can be compiled with something like `g++ -DFACT=20 -std=c++11 fact.cpp`
constexpr long long factorial(int n)
{
return (n ? (n * factorial(n - 1)) : 1);
}
constexpr long long fact = factorial(FACT);
int main()
{
cout << fact << endl;
return 0;
}
What happens if FACT=21?