Floating-Point Non-Type Template Parameters in C++
This article provides an overview of the constraints and rules governing the use of floating-point non-type template parameters (NTTP) in C++. While early versions of the standard prohibited floating-point types in template parameter lists, C++20 officially added support for them under strict structural conditions. Below is a detailed breakdown of the requirements, equivalence guarantees, and edge-case behaviors that apply when working with floating-point NTTPs.
C++ Standard Version Requirement
Floating-point non-type template parameters are only permitted
starting in C++20. In earlier standards (C++98 through
C++17), non-type template parameters were restricted to integral types,
enumeration types, pointers, references, and
std::nullptr_t. Code using floating-point types such as
float, double, or long double as
template arguments will fail to compile on standards prior to C++20.
The Structural Type Requirement
Under C++20, an NTTP must be a structural type. A type is structural if it is:
- A scalar type (which includes standard floating-point types).
- An lvalue reference type.
- A literal class type meeting specific criteria regarding public, non-mutable subobjects.
Because standard floating-point types (float,
double, long double) are scalar types, they
inherently satisfy the structural type requirement.
Compile-Time Constant Expressions
Any floating-point value passed to a template must be a core
constant expression known at compile time. It must be
evaluatable during translation using constexpr semantics.
Runtime variables, non-constant functions, or operations that produce
undefined behavior during constant evaluation cannot be used as
arguments.
Template Argument Equivalence
A critical challenge with floating-point values in templates is determining whether two distinct template instantiations refer to the exact same type or function. C++20 establishes precise rules for template argument equivalence:
- Equality of Values: Two values of the same floating-point type generate the same template specialization if they evaluate to the same value.
- Signed Zeros (
+0.0and-0.0): In standard IEEE 754 arithmetic,+0.0 == -0.0is true, despite having different bit representations. In C++, template arguments+0.0and-0.0are considered template-argument-equivalent and yield the same specialization. - Not-a-Number (NaN): While IEEE 754 defines
NaN == NaNas false, C++ template rules treat two quietNaNvalues of the same type as template-argument-equivalent, resolving them to the same template specialization.
Implementation and Optimization Flags
Compilers rely on strict IEEE 754 compliance to maintain predictable
behavior across translation units. Using aggressive optimization
flags—such as -ffast-math or -Ofast in GCC and
Clang—can alter algebraic rules, zero signedness, and NaN handling. This
can cause non-standard behavior or inconsistencies in how floating-point
template arguments are evaluated at compile time.