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

In C++, Non-Type Template Parameters (NTTPs) allow for values like integers and pointers to be passed at compile time into templates. Examples illustrate using NTTPs for array sizes, function pointers, and enumeration constants, demonstrating the flexibility and compile-time evaluation of NTTPs in various contexts.

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

About the Author

Richard Lesh is a software engineer and computer-science educator specializing in high-performance computing (HPC), numerical algorithms, systems-level software development and artificial intelligence.

@ Richard Lesh. All rights reserved.