Chapter 3

Nontype Template Parameters

For function and class templates, template parameters don’t have to be types. They can also be ordinary values. As with templates using type parameters, you define code for which a certain detail remains open until the code is used. However, the detail that is open is a value instead of a type. When using such a template, you have to specify this value explicitly. The resulting code then gets instantiated. This chapter illustrates this feature for a new version of the stack class template. In addition, we show an example of nontype function template parameters and discuss some restrictions to this technique.

3.1 Nontype Class Template Parameters

In contrast to the sample implementations of a stack in previous chapters, you can also implement a stack by using a fixed-size array for the elements. An advantage of this method is that the memory management overhead, whether performed by you or by a standard container, is avoided. However, determining the best size for such a stack can be challenging. The smaller the size you specify, the more likely it is that the stack will get full. The larger the size you specify, the more likely it is that memory will be reserved unnecessarily. A good solution is to let the user of the stack specify the size of the array as the maximum size needed for stack elements.

To do this, define the size as a template parameter:

basics/stacknontype.hpp

#include <array>
#include <cassert>
 
template<typename T, std::size_t Maxsize>
class Stack {
  private:
    std::array<T,Maxsize> elems; // elements
    std::size_t numElems;        // current number of elements
  public:
    Stack();                     // constructor
    void push(T const& elem);    // push element
    void pop();                  // pop element
    T const& top() const;        // return top element
    bool empty() const {         //return whether the stack is empty
        return numElems == 0;
    }
    std::size_t size() const {    //return current number of elements
        return numElems;
    }
};
 
template<typename T, std::size_t Maxsize>
Stack<T,Maxsize>::Stack ()
 : numElems(0)                 //start with no elements
{
   // nothing else to do
}
 
template<typename T, std::size_t Maxsize>
void Stack<T,Maxsize>::push (T const& elem)
{
    assert(numElems < Maxsize);
    elems[numElems] = elem;    // append element
    ++numElems;                // increment number of elements
}
 
template<typename T, std::size_t Maxsize>
void Stack<T,Maxsize>::pop ()
{
    assert(!elems.empty());
    --numElems;                // decrement number of elements
}
 
template<typename T, std::size_t Maxsize>
T const& Stack<T,Maxsize>::top () const
{
    assert(!elems.empty());
    return elems[numElems-1];  // return last element
}

The new second template parameter, Maxsize, is of type int. It specifies the size of the internal array of stack elements:

template<typename T, std::size_t Maxsize>
class Stack {
  private:
    std::array<T,Maxsize> elems; // elements
    …
};

In addition, it is used in push() to check whether the stack is full:

template<typename T, std::size_t Maxsize>
void Stack<T,Maxsize>::push (T const& elem)
{
    assert(numElems < Maxsize);
    elems[numElems] = elem;   // append element     ++numElems;               // increment number of elements
}

To use this class template you have to specify both the element type and the maximum size:

basics/stacknontype.cpp

#include "stacknontype.hpp"
#include <iostream>
#include <string>
 
int main()
{
  Stack<int,20>         int20Stack;      // stack of up to 20 ints
  Stack<int,40>         int40Stack;      // stack of up to 40 ints
  Stack<std::string,40> stringStack;     // stack of up to 40 strings
 
  // manipulate stack of up to 20 ints
  int20Stack.push(7);
  std::cout << int20Stack.top() << ’\n’;
  int20Stack.pop();
 
  // manipulate stack of up to 40 strings
  stringStack.push("hello");
  std::cout << stringStack.top() << ’\n’;
  stringStack.pop();
}

Note that each template instantiation is its own type. Thus, int20Stack and int40Stack are two different types, and no implicit or explicit type conversion between them is defined. Thus, one cannot be used instead of the other, and you cannot assign one to the other.

Again, default arguments for the template parameters can be specified:

template<typename T = int, std::size_t Maxsize = 100>
class Stack {
  …
};

However, from a perspective of good design, this may not be appropriate in this example. Default arguments should be intuitively correct. But neither type int nor a maximum size of 100 seems intuitive for a general stack type. Thus, it is better when the programmer has to specify both values explicitly so that these two attributes are always documented during a declaration.

3.2 Nontype Function Template Parameters

You can also define nontype parameters for function templates. For example, the following function template defines a group of functions for which a certain value can be added:

basics/addvalue.hpp

template<int Val, typename T>
T addValue (T x)
{
  return x + Val;
}

These kinds of functions can be useful if functions or operations are used as parameters. For example, if you use the C++ standard library you can pass an instantiation of this function template to add a value to each element of a collection:

std::transform (source.begin(), source.end(),  //start and end of source
                dest.begin(),                  //start of destination
                addValue<5,int>);              // operation

The last argument instantiates the function template addValue<>() to add 5 to a passed int value. The resulting function is called for each element in the source collection source, while it is translated into the destination collection dest.

Note that you have to specify the argument int for the template parameter T of addValue<>(). Deduction only works for immediate calls and std::transform() need a complete type to deduce the type of its fourth parameter. There is no support to substitute/deduce only some template parameters and the see, what could fit, and deduce the remaining parameters.

Again, you can also specify that a template parameter is deduced from the previous parameter. For example, to derive the return type from the passed nontype:

template<auto Val, typename T = decltype(Val)>
T foo();

or to ensure that the passed value has the same type as the passed type:

template<typename T, T Val = T{}>
T bar();

3.3 Restrictions for Nontype Template Parameters

Note that nontype template parameters carry some restrictions. In general, they can be only constant integral values (including enumerations), pointers to objects/functions/members, lvalue references to objects or functions, or std::nullptr_t (the type of nullptr).

Floating-point numbers and class-type objects are not allowed as nontype template parameters:

template<double VAT>         // ERROR: floating-point values are not
double process (double v)    //        allowed as template parameters
{
    return v * VAT;
}
 
template<std::string name>   // ERROR: class-type objects are not
class MyClass {              //        allowed as template parameters
  …
};

When passing template arguments to pointers or references, the objects must not be string literals, temporaries, or data members and other subobjects. Because these restrictions were relaxed with each and every C++ version before C++17, additional constraints apply:

• In C++11, the objects also had to have external linkage.

• In C++14, the objects also had to have external or internal linkage.

Thus, the following is not possible:

template<char const* name>
class MyClass {
  …
};
 
MyClass<"hello"> x;  //ERROR: string literal "hello" not allowed

However there are workarounds (again depending on the C++ version):

extern char const s03[] = "hi";    // external linkage
char const s11[] = "hi";           // internal linkage
 
int main()
{
  Message<s03> m03;                // OK (all versions)
  Message<s11> m11;                // OK since C++11
  static char const s17[] = "hi";  // no linkage
  Message<s17> m17;                // OK since C++17
}

In all three cases a constant character array is initialized by "hello" and this object is used as a template parameter declared with char const*. This is valid in all C++ versions if the object has external linkage (s03), in C++11 and C++14 also if it has internal linkage (s11), and since C++17 if it has no linkage at all.

See Section 12.3.3 on page 194 for a detailed discussion and Section 17.2 on page 354 for a discussion of possible future changes in this area.

Avoiding Invalid Expressions

Arguments for nontype template parameters might be any compile-time expressions. For example:

template<int I, bool B>
class C;

C<sizeof(int) + 4, sizeof(int)==4> c;

However, note that if operator> is used in the expression, you have to put the whole expression into parentheses so that the nested > ends the argument list:

C<42, sizeof(int) > 4> c;    // ERROR: first > ends the template argument list
C<42, (sizeof(int) > 4)> c;  // OK

3.4 Template Parameter Type auto

Since C++17, you can define a nontype template parameter to generically accept any type that is allowed for a nontype parameter. Using this feature, we can provide an even more generic stack class with fixed size:

basics/stackauto.hpp

#include <array>
#include <cassert>
 
template<typename T, auto Maxsize>
class Stack {
  public:
    using size_type = decltype(Maxsize);
  private:
    std::array<T,Maxsize> elems; // elements
    size_type numElems;          // current number of elements
  public:
    Stack();                     // constructor
    void push(T const& elem);    // push element
    void pop();                  // pop element
    T const& top() const;        // return top element
    bool empty() const {         //return whether the stack is empty
        return numElems == 0;
    }
    size_type size() const {     //return current number of elements
        return numElems;
    }
};
 
// constructor
template<typename T, auto Maxsize>
Stack<T,Maxsize>::Stack ()
 : numElems(0)                 //start with no elements
{
    // nothing else to do
}
 
template<typename T, auto Maxsize>
void Stack<T,Maxsize>::push (T const& elem)
{
    assert(numElems < Maxsize);
    elems[numElems] = elem;   // append element
    ++numElems;               // increment number of elements
}
 
template<typename T, auto Maxsize>
void Stack<T,Maxsize>::pop ()
{
    assert(!elems.empty());
    --numElems;                // decrement number of elements
}
 
template<typename T, auto Maxsize>
T const& Stack<T,Maxsize>::top () const
{
    assert(!elems.empty());
    return elems[numElems-1];  // return last element
}

By defining

template<typename T, auto Maxsize>
class Stack {
  …
};

by using the placeholder type auto, you define Maxsize to be a value of a type not specified yet. It might be any type that is allowed to be a nontype template parameter type.

Internally you can use both the value:

std::array<T,Maxsize> elems;     // elements

and its type:

    using size_type = decltype(Maxsize);

which is then, for example, used as return type of the size() member function:

size_type size() const {       //return current number of elements
    return numElems;
}

Since C++14, you could also just use auto here as return type to let the compiler find out the return type:

auto size() const {        //return current number of elements
    return numElems;
}

With this class declaration the type of the number of elements is defined by the type used for the number of elements, when using a stack:

basics/stackauto.cpp

#include <iostream>
#include <string>
#include "stackauto.hpp"
 
int main()
{
  Stack<int,20u>        int20Stack;     // stack of up to 20 ints
  Stack<std::string,40> stringStack;    // stack of up to 40 strings
 
  // manipulate stack of up to 20 ints
  int20Stack.push(7);
  std::cout << int20Stack.top() << ’\n’;
  auto size1 = int20Stack.size();
 
  // manipulate stack of up to 40 strings
  stringStack.push("hello");
  std::cout << stringStack.top() << ’\n’;
  auto size2 = stringStack.size();
 
  if (!std::is_same_v<decltype(size1), decltype(size2)>) {
    std::cout << "size types differ" << ’\n’;
  }
}

With

Stack<int,20u> int20Stack;        // stack of up to 20 ints

the internal size type is unsigned int, because 20u is passed.

With

Stack<std::string,40> stringStack;   // stack of up to 40 strings

the internal size type is int, because 40 is passed.

size() for the two stacks will have different return types, so that after

    auto size1 = int20Stack.size();
    …
    auto size2 = stringStack.size();

the types of size1 and size2 differ. By using the standard type trait std::is_same (see Section D.3.3 on page 726) and decltype, we can check that:

if (!std::is_same<decltype(size1), decltype(size2)>::value) {
  std::cout << "size types differ" << ’\n’;
}

Thus, the output will be:

size types differ

Since C++17, for traits returning values, you can also use the suffix _v and skip ::value (see Section 5.6 on page 83 for details):

if (!std::is_same_v<decltype(size1), decltype(size2)>) {
  std::cout << "size types differ" << ’\n’;
}

Note that other constraints on the type of nontype template parameters remain in effect. Especially, the restrictions about possible types for nontype template arguments discussed in Section 3.3 on page 49 still apply. For example:

Stack<int,3.14> sd;        // ERROR: Floating-point nontype argument

And, because you can also pass strings as constant arrays (since C++17 even static locally declared; see Section 3.3 on page 49), the following is possible:

basics/message.cpp

#include <iostream>
 
template<auto T>       // take value of any possible nontype parameter (since C++17)
class Message {
  public:
    void print() {
      std::cout << T << ’\n’;
    }
};
 
int main()
{
  Message<42> msg1;
  msg1.print();        // initialize with int 42 and print that value
 
  static char const s[] = "hello";
  Message<s> msg2;     // initialize with char const[6] "hello"
  msg2.print();        // and print that value
}

Note also that even template<decltype(auto) N> is possible, which allows instantiation of N as a reference:

template<decltype(auto) N>
class C {
  …
};
 
int i;
C<(i)> x;  // N is int&

See Section 15.10.1 on page 296 for details.

3.5 Summary

• Templates can have template parameters that are values rather than types.

• You cannot use floating-point numbers or class-type objects as arguments for nontype template parameters. For pointers/references to string literals, temporaries, and subobjects, restrictions apply.

• Using auto enables templates to have nontype template parameters that are values of generic types.