Let's see an example of creating our first template. We'll start with variable templates.
Variable templates are perhaps the least useful, but they're the most simple to create.
Let's imagine we were creating a library for our colleagues to use. Our library needs mathematical constants, such as pi
:
constexpr double Pi {
3.141592653589793238462643383279
}
But, how did we decide to use a double
here? We don’t know how our library is going to be used by our consumers, so the preferred type was really just a guess. Maybe they’d prefer float
? We could create different versions of this:
constexpr int PiInt {
3
};
constexpr double PiFloat {
3.141592f
};
constexpr double PiDouble {
3.141592653589793
};
There are some disadvantages here. Aside from the code duplication and the verbose names, this approach requires us to know all the types our users will want in advance.
This may not be possible to know. For example, anyone using our Pi code could be defining their own numeric types, rather than using the built-in types.
So, variable templates can help us here.
To create a variable template, let's replace the type with a template parameter. To do this, we state that we're going to use a template in place of a type name. For now, we will call that parameter SomeType
.
template <typename SomeType>
Then, anywhere we want to refer to that type, we will instead refer to our template parameter:
template <typename SomeType>
constexpr SomeType Pi {
3.141592653589793238462643383279
};
We can call our parameter whatever we want but, by convention, templated types are often called T
:
template <typename T>
constexpr T Pi {
3.141592653589793238462643383279
};
Now, we can ask our variable template to create new variables, using the <
and >
syntax. Given that the template requires a type as its parameter, we need to specify what type the variable should be:
Pi<int>;
Pi<float>;
Pi<double>;
Pi<SomeCustomType>;
Any time the compiler encounters the Pi
template being invoked with a type that it hasn't seen, it will use the template to create a new variable, passing the double
3.141... to that type's constructor.
So, for Pi<SomeCustomType>
to be valid, SomeCustomType
would need to have a constructor that can accept a double
.
At this point, a reasonable question would be to ask why we need to use variable templates here at all. For example, if we have an int
and we need to pass it to a function’s double
parameter, we can just do that without templates. The int
will be converted to a double
.
The problem is that a conversion done this way may have performance impacts at runtime. This can be particularly problematic for low-level functions that may be called hundreds of times per frame.
With variable templates, the conversion is done one time: when our code is compiled. Therefore, there is no performance impact.
In the above example, the compiler will create 4 new variables.
In isolation, a statement like Pi<double>;
is not particularly useful. However, this expression also returns the value of the variable that was created, so they can immediately be used like any other value:
auto MyValue { Pi<int> };
SomeFunction(Pi<float>);
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.