C++ Metaprogramming: Non-Type Template Parameters (NTTP)

Introduction to C++ metaprogramming using templates and non-type template parameters.

In C++, Non-Type Template Parameters (NTTPs) are a type of template parameter that represent values (rather than types). These values are specified when a template is instantiated, and they can be used just like regular constants within the template. Non-type template parameters can represent things like integers, pointers, or references, and they are evaluated at compile time.

IntNTTPExample.cpp
#include <iostream>

template<int N>
class Array {
    int arr[N];             // Array with size N
public:
    Array() {
        fill(0);
    }

    void fill(int value) {
        for (int i = 0; i < N; ++i) {
            arr[i] = value;
        }
    }

    void print() const {
        for (int i = 0; i < N; ++i) {
            std::cout << arr[i] << " ";
        }
        std::cout << std::endl;
    }
};

int main() {
    Array<5> myArray;       // Array of size 5
    myArray.fill(10);
    myArray.print();

    constexpr int size = 3; // Must be a constant expression
    Array<size> myArray2;   // Array of size 3
    myArray2.fill(30);
    myArray2.print();

    return 0;
}
10 10 10 10 10
30 30 30

Note in this example the template parameter N isn't declared as template or class as ordinary template parameters that represent types are declared. Instead it has a specific type of int in this case. When the class is instantiated, a specific compile-time integer must be supplied.

Array<> classes instantiated with different NTTP integer values are not the same type, i.e. they can not be assigned to the other.

Compile-time pointers such as pointers to functions can also be used as NTTP in templates. In the next example we create a template function that takes a NTTP parameter that points to a function.

PointerNTTPExample.cpp
#include <iostream>

void printHello() {
    std::cout << "Hello" << std::endl;
}

void printGoodbye() {
    std::cout << "Goodbye" << std::endl;
}

template<void (*Func)()>
class CallFunction {
public:
    void call() const {
        Func();  		// Call the function pointed to by Func
    }
};

int main() {
    CallFunction<&printHello> helloCaller;
    CallFunction<&printGoodbye> goodbyeCaller;

    helloCaller.call();
    goodbyeCaller.call();

    return 0;
}
Hello
Goodbye

In the CallFunction<>() template function, the template parameter is a literal function address. This allows each instantiation of CallFunction<>() to call a different function.

In the next example we see that even an enumeration constant can be used as an NNTP.

EnumNTTPExample.cpp
#include <iostream>

enum class Color { Red, Green, Blue };

template<Color C>
class Paint {
public:
    void apply() const {
        if (C == Color::Red)
            std::cout << "Painting in Red" << std::endl;
        else if (C == Color::Green)
            std::cout << "Painting in Green" << std::endl;
        else
            std::cout << "Painting in Blue" << std::endl;
    }
};

int main() {
    Paint<Color::Red> redPainter;
    Paint<Color::Green> greenPainter;

    redPainter.apply();
    greenPainter.apply();

    return 0;
}
Painting in Red
Painting in Green