Template Value Parameter Usage

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.

Index