Non-Type Template Parameters in C++20

This article explores Non-Type Template Parameters (NTTPs) in C++, explaining what they are, their historical limitations, and the major enhancements introduced in C++20. Readers will gain a clear understanding of how C++20 expands NTTP capabilities to support floating-point values, user-defined class types (structural types), and compile-time string literals, enabling cleaner and more expressive compile-time metaprogramming.

What Are Non-Type Template Parameters?

In C++, templates typically accept types (such as int, std::string, or custom classes) using the typename or class keywords. A Non-Type Template Parameter (NTTP), by contrast, is a template parameter that represents a compile-time constant value rather than a type.

Prior to C++20, the types permitted as NTTPs were strictly limited. Permissible NTTP types included:

  • Integral types (e.g., int, char, size_t)
  • Enumeration types
  • Pointers and references to objects or functions with linkage
  • std::nullptr_t

The most common example of a traditional NTTP is the second parameter of std::array:

template <typename T, std::size_t N>
class array;

std::array<int, 10> numbers; // 10 is an NTTP

Historically, attempts to pass floating-point numbers, objects with complex state, or string literals directly as template arguments resulted in compilation errors.


How C++20 Expanded NTTPs

C++20 significantly relaxed these restrictions by introducing support for floating-point types, structural class types, and custom compile-time strings.

1. Floating-Point Numbers as NTTPs

C++20 permits floating-point types (float, double, long double) to serve directly as template parameters. This eliminates the need for workarounds such as passing ratios or scaled integers for compile-time scientific calculations.

template <double Threshold>
struct SensorFilter {
    bool isAbove(double value) {
        return value > Threshold;
    }
};

SensorFilter<0.75> filter;

2. Structural Class Types

The most profound addition to NTTPs in C++20 is the concept of structural types. An object of a class type can be used as a template argument if it qualifies as a structural type.

A type is a structural type if it is:

  • A scalar type, or
  • An lvalue reference type, or
  • A literal class type where:
    • All base classes and non-static data members are public and non-mutable, and
    • The types of all bases and non-static data members are themselves structural types.
struct Point {
    int x;
    int y;
};

template <Point P>
void render() {
    // P.x and P.y are accessible at compile time
}

int main() {
    render<Point{10, 20}>(); // Valid in C++20
}

Two template instantiations are considered identical if the bitwise or value-based equivalence of their structural arguments matches.

3. Compile-Time String Literals

A longstanding limitation of C++ templates was the inability to pass string literals directly into templates. Because C++20 allows structural class types as NTTPs, developers can define a literal wrapper class to accept and store string literals at compile time.

#include <algorithm>
#include <cstddef>

template <std::size_t N>
struct FixedString {
    char data[N];

    constexpr FixedString(const char (&str)[N]) {
        std::copy_n(str, N, data);
    }
};

template <FixedString S>
void printTag() {
    // S.data contains the string at compile time
}

int main() {
    printTag<"SystemLogger">(); // Valid in C++20
}

4. Class Template Argument Deduction (CTAD) for NTTPs

C++20 also introduced the auto placeholder for NTTPs, allowing the compiler to deduce the argument's type directly at the call site.

template <auto Value>
void inspect() {
    // Type and value are deduced at compile time
}

inspect<42>();      // Value is int
inspect<'c'>();     // Value is char
inspect<3.14>();    // Value is double (C++20)

Summary of Impact

C++20 transforms NTTPs from simple integer and pointer placeholders into a versatile system that accepts rich value-based data structures. By supporting floating-point types, structural classes, and compile-time strings, C++20 removes decades-old metaprogramming workarounds, resulting in safer, more expressive, and readable compile-time APIs.