(Note this topic is not related to template Partial specialization)
First, let’s distinguish a template parameter (like “T”) vs a template argument (like a class “Account”). For a NDTTP, the template parameter has a) concrete type and b) a parameter name, typically “size_t value” vs the template argument like “31”.
Simplest example: std::array template has 2 template parameters —
- a dummy type T
- a non-dummy type “size_t length”
std::array<Account, 31> concretizes the template with a concrete type Account and a value of 31.
Majority of class templates in practice have a dummy type (or multiple, but let’s stay focused) to signify an Unknown-type. For example, the STL vector can be “concretized” in memory to become a real CLASS when given a real type like “Account”. The real type replaces the dummy type. [[C++ primer]] is the first book I read that mentions a very powerful type of class template. I call it NDTTP i.e. non-dummy-type template parameter.
Background — What appears inside angle brackets after the keyword “template” is always a list of “tokens” or “thingies”. In most cases in practice, each thingy is a “typename T” or “class C”.
Now, the thingy can also be something like “int size”. This is known as a non-type template parameter. I call it a non-dummy-type template parameter, or NDT template parameter. a NDT is not a dummy type, but a real type with a parameter name. NDT declaration syntax is like a regular function parameter. NDT represents a dummy type pinned to a real type. But how is NDT used when the push comes to the shove i.e. template instantiation (I prefer “template concretization”)?
Remember a real type argument like “AccountAllocator” in STL vector is a piece of information at the class level, not Instance level. When the vector is concretized in memory to become a real/concrete Class, the real class uses AccountAllocator Class. In the same vein, the NDT value is at class-level, not class-instance level nor at template level. That implies the “int size” value of 55 is a constant for the class, across all class instances.
In other words, when we convert a GENERIC “unconcretized” template matrix_double into a real class, the “int size” template parameter (NDT template parameter) is replaced by a value like 55, and treated as a class-level static constant. If we construct 9999 instances of the concrete matrix_double class, all of them share the same size=55.
A class template can have all its “tokens” be dummy types (standard practice), or all of them NDT (P861 [[c++primer]]), or a mixture.
See other posts about When the NDT arg is a specific func ptr like “revalue”. In contrast, given a functor class, you use a regular type param (more common), not a NDTTP.