Template Value Parameter Usage
There are restrictions on the types available for use in template value parameters. A non-type parameter can be any of the following types:
- An integral type
- An enumeration type
- A pointer or reference to a class object
- A pointer or reference to a function
- A pointer or reference to a class member function
- std::nullptr_t
- A floating point type (since C++20)
Here are some examples of where value parameters can be used:
Compile-time Operations
//
// main.cpp
// Factorial Template
//
// Created by Bryan Higgs on 10/15/24.
//
#include <iostream>
template <int N>
constexpr int factorial()
{
int value = 1; // 0! = 1
for (int i = 1; i <= N; i++)
{
value *= i;
}
return value;
}
int main(int argc, const char * argv[])
{
std::cout << "5 factorial = "
<< factorial<5>() << std::endl;
std::cout << "10 factorial = "
<< factorial<10>() << std::endl;
return 0;
}This program produces the following output:
5 factorial = 120
10 factorial = 3628800
Program ended with exit code: 0
You can implement a recursive solution, too:
//
// main.cpp
// Factorial Template
//
// Created by Bryan Higgs on 10/15/24.
//
#include <iostream>
template <int N>
constexpr int factorial()
{
// A compile-time if which drops the non-hitting block immediately
if constexpr (N == 0) return 1;
else return N * factorial<N - 1>();
}
int main(int argc, const char * argv[])
{
std::cout << "5 factorial = "
<< factorial<5>() << std::endl;
std::cout << "10 factorial = "
<< factorial<10>() << std::endl;
return 0;
}Note that, in this case, the keyword constexpr is required, to ensure that the if statement is a compile-time construct. Without the constexpr, you get very confusing and peculiar results.
Allocating a Fixed-size Buffer
//
// main.cpp
// Fixed-size Array Template
//
// Created by Bryan Higgs on 10/15/24.
//
#include <iostream>
template <typename T, size_t size>
T* Buffer ( const T* initValues )
{
T* alloc = new T[size];
for (int i = 0; i < size; i++ )
{
alloc[i] = initValues[i];
}
return alloc;
}
template <typename T>
void printBuffer(const T* buf, size_t size)
{
std::cout << "Buffer: \n";
for (int i = 0; i < size; i++)
{
std::cout << " [" << i << "] " << buf[i]
<< std::endl;
}
}
int main(int argc, const char * argv[])
{
int initIntValues[] { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 };
int* intArray = Buffer<int, 10>( initIntValues );
printBuffer( intArray , 10);
double initDoubleValues[] { 27.3, 1.4, 5.2, 11.9, 71.2};
double* doubleArray = Buffer<double, 5>( initDoubleValues );
printBuffer( doubleArray, 5);
// Tidy up
delete [] intArray;
delete [] doubleArray;
return 0;
}This program produces the following output:
Buffer:
[0] 2
[1] 4
[2] 6
[3] 8
[4] 10
[5] 12
[6] 14
[7] 16
[8] 18
[9] 20
Buffer:
[0] 27.3
[1] 1.4
[2] 5.2
[3] 11.9
[4] 71.2
Program ended with exit code: 0
There are other uses for template value parameters, but many are rather esoteric and beyond the scope of this tutorial.