What Are Non-Type Template Parameters?

A non-type template parameter is a template parameter that represents a compile-time constant value (like an integer) rather than a type. This allows you to parameterize templates on values such as sizes, counts, or other constants known at compile time.

So far, template parameters have represented types. But templates can also accept values. A non-type template parameter (NTTP) is a placeholder for a compile-time constant value rather than a type.

You've already used one if you've worked with std::array:

std::array<double, 100> measurements{};  // 100 is a non-type template parameter

The 100 determines the array's size at compile time.

Declaring non-type template parameters

Declare an NTTP by specifying its type in the template parameter list:

template <int Rows, int Cols>
void createGrid()
{
    // Rows and Cols are compile-time constants here
}

Example usage:

#include <iostream>

template <int Repetitions>
void printStars()
{
    for (int i{}; i < Repetitions; ++i)
        std::cout << '*';
    std::cout << '\n';
}

int main()
{
    printStars<5>();   // *****
    printStars<10>();  // **********

    return 0;
}

The compiler generates separate functions for each value:

void printStars_5()    // Generated from printStars<5>
{
    for (int i{}; i < 5; ++i)
        std::cout << '*';
    std::cout << '\n';
}

void printStars_10()   // Generated from printStars<10>
{
    for (int i{}; i < 10; ++i)
        std::cout << '*';
    std::cout << '\n';
}

Allowed types for NTTPs

Non-type template parameters can be:

Type Example
Integral types int, std::size_t, char, bool
Enumeration types enum class Color { Red, Green, Blue }
std::nullptr_t nullptr
Floating-point (C++20) double, float
Pointers/references int*, const char*
Literal class types (C++20) Custom constexpr classes

Compile-time requirements

NTTP arguments must be known at compile time:

template <int Size>
void allocateBuffer()
{
    char buffer[Size];  // Size must be a compile-time constant
}

int main()
{
    allocateBuffer<256>();      // OK: literal is compile-time constant

    constexpr int size{ 512 };
    allocateBuffer<size>();     // OK: constexpr variable

    int userSize{};
    std::cin >> userSize;
    allocateBuffer<userSize>(); // Error: runtime value

    return 0;
}

Combining type and non-type parameters

Templates often mix both kinds:

#include <iostream>

template <typename T, int Count>
void repeat(T value)
{
    for (int i{}; i < Count; ++i)
        std::cout << value << ' ';
    std::cout << '\n';
}

int main()
{
    repeat<int, 3>(42);         // 42 42 42
    repeat<char, 5>('X');       // X X X X X
    repeat<double, 2>(3.14);    // 3.14 3.14

    return 0;
}

The type parameter T determines what kind of value to print. The non-type parameter Count determines how many times.

Practical application: fixed-size containers

NTTPs are ideal for compile-time sizing:

template <typename T, int Capacity>
class RingBuffer
{
private:
    T data[Capacity]{};
    int head{};
    int tail{};
    int count{};

public:
    void push(T value)
    {
        data[tail] = value;
        tail = (tail + 1) % Capacity;
        if (count < Capacity)
            ++count;
        else
            head = (head + 1) % Capacity;
    }

    T front() const { return data[head]; }
    int size() const { return count; }
    constexpr int capacity() const { return Capacity; }
};

Usage:

RingBuffer<int, 4> buffer{};
buffer.push(10);
buffer.push(20);
buffer.push(30);
// buffer.capacity() returns 4 at compile time

Compile-time validation with static_assert

NTTPs enable compile-time error checking:

#include <cmath>

template <int Divisor>
int safeDivide(int numerator)
{
    static_assert(Divisor != 0, "Divisor cannot be zero");
    return numerator / Divisor;
}

int main()
{
    safeDivide<5>(100);  // OK: returns 20
    safeDivide<0>(100);  // Compile error: static_assert fails

    return 0;
}

The error is caught during compilation, not at runtime.

Type deduction for NTTPs (C++17)

Use auto to let the compiler deduce the NTTP type:

#include <iostream>

template <auto Value>
void display()
{
    std::cout << Value << '\n';
}

int main()
{
    display<42>();       // Deduces int
    display<'A'>();      // Deduces char
    display<true>();     // Deduces bool
    display<3.14>();     // Deduces double (C++20)

    return 0;
}

Naming conventions

Use descriptive names when the purpose is clear:

template <int Rows, int Cols>      // Clear meaning
template <int BufferSize>          // Clear meaning
template <int MaxRetries>          // Clear meaning

For generic cases, N is conventional:

template <int N>                   // Generic integer parameter
template <typename T, int N>       // Common pattern

Summary

Non-type template parameters accept compile-time constant values:

Aspect Details
Declaration template <int N>, template <typename T, int N>
Argument requirements Must be compile-time constants
Common types int, std::size_t, bool, enums
C++17 feature auto for type deduction
C++20 additions Floating-point types, literal class types

Primary uses:

  • Fixed-size arrays and containers
  • Compile-time configuration
  • Static assertions for validation
  • Constant-based algorithm variations