C++ Template Programming

C++ Templates

C++ Templates

In this post, I will introduce you to C++ template programming. Some of the topics I will cover are function templates, class templates, variable templates, variadic templates, type traits, SFINAE, and C++ 20 concepts.

Contents

Introduction

C++ Template Programming has been around for a long time and there are plenty of books and articles on the internet that describe C++ template programming. Although the information is in abundance, there doesn’t seem to be a comprehensive source for learning how to apply template programming and demystify some of the more complex features of C++ template programming such as using and writing type traits, template meta programming, and Substitution Failure Is Not An Error (SFINAE) which are a few of the topics that I would like to cover in this article.

Terminology

Before delving into the details of C++ template programming, it is important to establish some common terminology when describing template programming.

Value categories are a way to categorize how a value can be used. There are five value categories (lvalue, prvalue, xvalue, glvalue, and rvalue). You may already be familiar with lvalue, and rvalue, but might not have encountered prvalue, xvalue, or glvalue yet.

Understanding the difference between template parameters and template arguments is also important when talking about template programming. In short, template parameters are used to declare the template and template arguments are used to define the template. Template parameters can be either typed, or non typed.

Let’s first look at value categories.

Expressions and Value Categories

All C++ programmers work with value categories but may not be able to describe which category a value belongs.

Value categories are often defined in terms of the result of an expression. An example of an expression is assignment, increment and decrement, arithmetic, logical, function calls, etc. An expression consists of one or more operands and one operator (or two operators in the case of the ternary operator). Expressions are usually terminated with a semicolon (;) but can also be nested inside other expressions or used inside conditional constructs (like if, while, and for loops).

A C++ Expression is a sequence of operators and operands that produce a result.

Expressions are categorized according to the following taxonomy:

Expressions are categorized according to the following taxonomy.[6]

According to Stroustrup[2], value categories can be identified by two independent properties:

  1. “has identity” (i): A value is identified by a name. Pointers and references also represent identities.
  2. “can be moved from” (m): A value can be moved. Expressions that use move semantics like the move constructor and the move assignment operator are examples of move expressions.

When a value category has the identity property, it can be denoted with a lower-case i. When a value type does not have the identity property, it will be denoted with an upper-case I.
Similarly for the move property: if the value category can be moved, it will be denoted with a lower-case m, otherwise it will be denoted with an upper-case M.

There are five value categories (three primary, and two mixed categories) that are discussed in this section:

  • Primary categories
    • lvalue
    • prvalue
    • xvalue
  • Mixed categories
    • glvalue
    • rvalue

lvalue, prvalue, and xvalue are primary value categories because they are mutually exclusive. glvalue and rvalue categories are mixed categories because they are a generalization of the primary value categories.

lvalue

An lvalue is a value that is named by an identifier (i) but cannot be moved (M). An lvalue is something that has a location in memory. For this reason, lvalues are sometimes referred to as locator values.

The following code snippet shows some examples of lvalues.

It might be tempting to think of lvalues as something that only appears on the left-hand side of an assignment operator, but this is not a good way to think of them. For example, a const value is an lvalue, but it cannot be assigned to after initialization.

Of course, lvalues can also appear on the right side of the assignment operator.

However, calling i an lvalue in this context (when it appears on the right side of the assignment operator) is not entirely correct. In this case, i is implicitly cast to an rvalue (the contents of i) which is what gets assigned to j[4].

An easy way to remember if something is an lvalue is to ask the question “is it possible to take the address of this value?”. If the answer is yes, then the value is an lvalue.

prvalue

A prvalue is a pure rvalue. Pure rvalues do not have an identifier (I) but can be moved (m). Literal values are examples of prvalues.

It is possible to assign a prvalue to an lvalue (as demonstrated in the previous code example) but it is not possible to assign an lvalue to a prvalue.

It is also an error to try to get the address of a prvalue.

The result of an expression using built-in operators are also prvalues.

It is possible to override the built-in operators to return non-prvalues, but that is not important in order to describe value categories.

One special thing to note is that prvalues can be candidates for move operations. The following example is valid:

This code compiles fine since the prvalue (3) is moved into the i parameter in the MoveMe function.

xvalue

An xvalue are values that are named using an identifier (i) and are movable (m). Any function that returns an rvalue reference (such as std::move) is an xvalue expression. xvalues are the result of casting an lvalue to an rvalue reference as shown in the following example:

The result of std::move is an xvalue and can be assigned to an rvalue reference.

The xvalue is referred to as an “expiring value” since it is used to refer to an object that is expiring since its resources are likely only going to be moved to another object soon[5].

The xvalue value category is somewhat esoteric and likely only important for compiler writers and people working on the C++ standard documentation. For this article, it’s only important to know of its existence.

glvalue

A glvalue is a “generalized lvalue”[2]. glvalues can be either an lvalue or an xvalue. You can think of it as a movable (m) lvalue. glvalues can also be named by an identifier (i). Some examples of glvalues are:

A glvalue is anything that is not a prvalue.

rvalue

An rvalue is a generalization of prvalues and xvalues. An rvalue is not named with an identifier (I) but can be moved (m). To avoid ambiguity with prvalues, rvalues are only denoted with the lower-case (m) to indicate the value “can be moved from”.

An rvalue is anything that is not an lvalue.

This images shows the various value categories and their corresponding properties as depicted by Bjarne Stroustrup[2].

Template Arguments versus Template Parameters

Suppose we have the following template class:

And we also have the following instantiation of the template class:

In this example, the template parameters are T and N and the type template argument is float and the non-type template arguments are 3, and 2. You can say that “parameters are initialized by arguments“.

Unlike function arguments, value of non-type template arguments must be determined at compile-time and the definition of a template with its arguments is called the template-id.

Each parameter in a template parameter list can be one of following types:

  • A type template parameter
  • A non-type template parameter
  • A template template parameter

The Array class template demonstrates the use of both type (T), and non-type (N) template parameters. In the next section, all three parameter types are explored.

Type Template Parameter

The most common template parameter is a type template parameter. A type template parameter starts with either typename or class and (optionally) followed by the parameter name. The name of the parameter is used as an alias of the type within the template and has the same naming rules as an identifier used in a typedef or a type alias.

A type template parameter may also define a default argument. If a type template parameter defines a default value, it must appear at the end of the parameter list.

A type template parameter can also be a parameter pack. A parameter pack starts with typename... (or class...) and is used to list an unbounded number of template parameters. Since parameter packs apply to all template parameter categories, parameter packs are discussed in the section about template parameter packs.

The following example demonstrates the use of type template parameters.

Non-type Template Parameter

Non-type template parameters can be used to specify a value rather than a type in the template parameter list.

Non-type template parameters can be:

  • An integral type (bool, char, int, size_t, and unsigned variants of those types)
  • An enumeration type
  • An lvalue reference type (a references to an existing object or function)
  • A pointer type (a pointer to an existing object or function)
  • A pointer to a member type (a pointer to a member object or a member function of a class)
  • std::nullptr_t

C++20 also adds floating-point types and literal class types (with some limitations explained here) to the list of allowed non-type template parameters.

Similar to type template parameters, the name of the template parameter is optional and non-type template parameters may also define default values.

The following shows examples of non-type template parameters:

Non-type template parameters must be constant values that are evaluated at compile-time.

Template Template Parameter

Templates can also be used as template parameters:

Note that until C++17, unlike type template parameters, template template parameters can only use the keyword class and not typename.

Template Parameter Pack

A template parameter pack is a placeholder for zero or more template parameters. A template parameter pack can be applied to type, non-type, and template template parameters but the parameter types cannot be mixed in a single parameter pack.

A few examples of template parameter packs:

A pack that is followed by an ellipsis expands the pack. A pack is expanded by replacing the pack with a comma-separated list of the template arguments in the pack.

For example, if a function template is defined with a parameter pack:

And invoking the function:

Will result in the following expansion:

More examples of using template parameter packs are shown later in the section about variadic templates.

Basics

The following sections introduce the basics of templates. If you are already familiar with templates, then you may want to skip to the next section.

Function Templates

Function templates provide a mechanism to define a function that operates on different types. Function templates look like ordinary functions but start with the template keyword followed by a list of (one or more) template parameters surrounded by angle brackets.

The max function accepts a single template parameter T. The max function template defines a family of functions that can take different types. The type is defined when the function is invoked either by explicitly specifying the type or by allowing the compiler to deduce the type:

On the first line, int is explicitly provided as the template argument. On the second line, the template argument is not specified but the complier automatically deduces it as int because the 3 and the 5 are deduced as int. In both cases, the same function is instantiated.

Implicit template type deduction does not work if you want to mix types as in the following case:

In this case, the max function template is being instantiated with 3 (int) and 5.0 (double) and the compiler does not know how to implicitly determine the template argument. In this case, the compiler will generate an error. There are a few ways this can be fixed:

  1. Use an explicit template argument
  2. Cast all function arguments to the same type
  3. Multiple template parameters

Explicitly specifying the template arguments will ensure that all of the parameters are cast to the correct type through implicit casting:

3 is implicitly cast to a double. In this case, the compiler may not even issue a warning since this type of cast does not cause any truncation of the original type. However, if a narrowing conversion occurs, the compiler will very likely generate a warning. To avoid this warning, an explicit cast can be used:

In this example, an explicit cast is used to convert 5.0 from a double to an int. The compiler no longer generates a warning and T is implicitly deduced to int.

The other solution to this problem is to allow a and b to be different types:

Now a and b can be different types and we can call the function template with mixed types without the compiler issuing any warnings… right? What about the return type? If T is “narrower” than U then the compiler will have to perform a narrowing conversion again and likely issue a warning when this happens. So what should the return type be? Is it possible to let the compiler decide?

Since the compiler will do anything to prevent data loss, it will try to convert all arguments to the largest (widest) type before performing the comparison and the return type will be the widest type. We can use the auto return type deduction, trailing return type, and the decltype specifier to automatically determine the safest type to use for the return value of the function template:

The trailing return type can be omitted in C++14 and higher.
This version of the max function template may still generate a warning about returning a reference to a temporary, but we can use type traits to avoid this. Type traits are discussed later so I won’t complicate this example more than necessary for now.

Using this version of the function template, any type can be used for a and b and the return value is the widest type of a or b. For example:

x is automatically deduced to double because comparing 3.0 (double) and 5 (int) results in a conversion to double and the max function template returns double.

The decltype specifier is explained in more detail later.

There is also a solution to determine the return type using std::common_type but requires knowledge of type traits which is discussed later.

Class Templates

Similar to Function Templates, classes can also be parameterized with one or more template parameters. The most common use case for class templates are containers for other types. If you have used any of the container types in the Standard Template Library (STL) (such as std::vector, std::map, or std::array), then you have already used class templates. In this section, I describe how to create class templates.

Consider the Array class template from the previous section. Here it is again for clarity:

A class template that doesn’t specialize any template parameters is called the primary template.

The Array class template demonstrates two kinds of template parameters:

  1. Type template parameters (denoted with typename or class)
  2. Non-type template parameters (denoted with an integral type such as size_t)

The Array class template defines a simple container for a static (fixed-size) array similar to the std::array implementation from the STL.

Inside the Array class template, T can be used wherever a type is expected (such as the declaration of the m_Data member variable or the return value of a member function) and N can be used wherever the number of elements is required (such as in the assert‘s in the index operator member functions).

A class template is instantiated when a variable that uses the class is defined:

Here, Array<float, 3> represents the type of the Position variable and Array<float, 2> is the type of the TexCoord variable. Although both types are instantiated from the same class template, they are in no way related. You cannot use Array<float, 3> where an Array<float, 2> is expected. For example, the following code will not compile:

Although this is a pretty contrived example, it demonstrates that different combinations of template arguments create different (unrelated) types.

Variable Templates

Variable templates were added to the C++ standard with C++14. Variable templates allow you to define a family of variables or static data members of a class using template syntax.

The variable template pi can now be used with varying levels of precision:

Will print:

Variable templates can also have both type and non-type template parameters:

T is a type template parameter and N is a non-type template parameter of type T.

It’s important to understand that a variable template does not define a type, but rather a value that is evaluated at compile-time.

Variable templates can also be specialized:

The Fib variable template computes the Nth Fibonacci number.

This will print 55 to the console.

Variable templates can also be used as a limited form of type traits:

If T is an integral type then is_integral<T> is true. For all other types, is_integral<T> is false.

Type-traits are discussed in more detail later in the article.

Alias Template

Templates can be aliased using the using keyword:

On line 5, bool_constant is defined as an alias template of the integral_constant variable template where T is bool. The value v remains open as a non-type template parameter.

typename Keyword

Besides being used as the keyword used to introduce a type template parameter, the typename keyword is also used in a class or function template definition to declare that a type is dependent on a template parameter.

For example, suppose we have the following class template:

The MyClassTemplate class template has a single template parameter (T) and type is a type alias of T.

Now suppose we have a function template that uses MyClassTemplate:

The MyFuncTemplate function template has a single template parameter (U) and declares a local variable (b) whose type is MyClassTemplate<U>::type. Since MyClassTemplate<U>::type names a type and that type is dependent on the template parameter (U), then MyClassTemplate<U>::type must be proceeded by typename:

The need for the typename keyword in this case, can be avoided by using an alias template:

The typename keyword is used to name a type that is dependent on a template parameter unless it was already established as a type (by using a typedef or a (template) type alias.

Template Specialization

Both function templates and class templates can be specialized for specific types. When all template parameters are specialized, then it is called fully specialized. Suppose we have the following function template definition:

The function template can be specialized by declaring the function with an empty template parameter list template<> and specifying the specialized template arguments after the function name:

All occurrences of template parameters (T and U) in the function must also be replaced with the specialized template arguments (double).

It is possible to provide the same functionality by using function overloading:

It is perfectly legal to overload function templates in this way.

The compiler will use the specialized (or overloaded) version of the function template if all of the substituted template arguments (either explicitly or implicitly) match the specialized version:

Similar to function templates, class templates can also be specialized. If we take the Array class template from the Class Template section and we want to specialize it for 4-component floating-point values:

The specialized version of the Array class template allows you to provide a different implementation of the class depending on its template arguments. In this case, we provide a specialization for Array<float, 4> that allows for some SSE optimizations to be made.

It is important to note that if you specialize a class template, you must also specialize all of the member functions of that class. This can be quite cumbersome for large classes, especially if you decide to refactor the generic class template, you must also update all specialized versions of the class.

Keep in mind that the compiler will only generate code for class template member functions that are used. That is, if you never call a specific member function of a specialized class template, then no code will be generated for that version of the member function. If a specialized class template defines a member function that just doesn’t make any sense for a certain specialized type, and you are sure that the member function is not being used anywhere in the codebase, then you can leave that function undefined in the specialized version of the class template.

Partial Specialization

Although it is not possible to partially specialize function templates, we can achieve something similar by using function template overloading. Let’s consider the max function template introduced in the previous section on Function Templates. Suppose we want to provide an implementation for pointer types:

This version of the function template is used whenever pointers are used as the arguments to the function as in the following example:

Strictly speaking, this is not partial specialization because none of the template parameters are specialized. We’re just providing a specific implementation when the types exhibit specific characteristics.

Unlike function templates, class templates can be partially specialized. Special implementations of class templates can be created to handle specific situations. Consider the Array class template from before. A special implementation can be created if the array holds pointer types:

A class template can be partially specialized by specifying the template keyword followed by a list of template parameters surrounded by angle brackets, just as with the non-specialized class template. The specialized template parameters are specified after the class name (T* and N in this case).

This implementation of the Array class template will be used when T is a pointer type. This may not seem like a very useful thing, but now we have a class template that can provide all the functionality of the original Array class template, but instead of allocating a static array, it now works with arbitrary data that is allocated elsewhere.

Similarly to a fully specialized class template, if one of the template parameters is fully defined then it does not need to be listed in the template parameter list, but must be specified in the template argument list (after the class name):

Partial template specialization is the cornerstone of type traits and SFINAE.

Variadic Templates

Variadic templates are function templates or class templates that can accept an unbounded number of template arguments.

For example, suppose we want a function that creates an std::unique_ptr from an arbitrary type:

For some reason, std::make_shared was introduced in C++11 but std::make_unique wasn’t introduced until C++14. This example provides a possible implementation of std::make_unique for C++11 compilers.

There are a few things to note here:

  1. The template parameter list contains a template parameter pack in the form of typename... Args
  2. An arbitrary number of arguments are passed to the function in the form of Args&&.... Not to be mistaken by an rvalue reference, this is called a forwarding reference which preserves the value category of the function arguments when used in conjunction with std::forward
  3. The arguments are unpacked by performing a pack expansion which replaces the parameter pack by a comma-separated list of the arguments using the pattern immediately preceding the ... (ellipsis)

For example, suppose we have the following class:

And if the make_unique function template was invoked with:

Then the make_unique function would generate something like this:

Recursive Variadic Templates

Suppose you want to write a function that prints an arbitrary number of arguments to the standard output stream. Using a C++17 compiler, this can be accomplished using fold expressions:

But how could this be accomplished without a C++17 compiler? To accomplish this without fold expressions, we need to create a recursive template function. To do this we must:

  1. Define the base case (only a single template parameter)
  2. Define the recursive case (where multiple template parameters are passed in a parameter pack)

First, let’s define the case where only a single argument is passed:

And the recursive case with a parameter pack:

The subtle trick here is that the recursive case has two template parameters:

  1. typename Arg
  2. typename... Args

This way, the first argument can be extracted from the parameter pack and the rest of the arguments are passed to the recursive print function. When there is only a single argument left in the parameter pack, the base case (with a single template argument) is used.

At this point, you should have a pretty good idea of how to use templates in your code. In the following sections, I will show a few more complex uses of templates.

Template Type Deduction

Template type deduction is the process the compiler performs to determine the type that is used to instantiate a function or class template. Many programmers use templates with a reasonable amount of success without really understanding how template type deduction works. This might be sufficient for simple use cases but becomes complicated (and perhaps unintuitive) in more complex applications of templates.

Understanding template type deduction forms the foundation for understanding how the decltype specifier works.

Scott Meyers provides a very good explanation of how type deduction works[7]. I will attempt to summarize Scott Meyers’ explanation here.

As we’ve seen in previous examples, function templates have the following basic form:

In the snippet above, T and ParamType may be different in the case that ParamType has modifiers (const, pointer (*), or reference (&) qualifiers). For example, if the template is declared like this:

Now suppose the template function is invoked like this:

In this case, T is deduced to int and ParamType is deduced to const int&.

In this case, it seems obvious that T is deduced to int since f was invoked with an int argument, but it’s not always that obvious. The type deduced for T is dependent on not only the argument type, but also the form of ParamType. There are three forms of ParamType that must be considered:

  1. ParamType is a pointer or reference type, but not a forwarding reference
  2. ParamType is a forwarding reference
  3. ParamType is neither a pointer nor a reference
Scott Meyers uses the term universal reference to refer to the double ampersand (&&) being applied to template parameters (not to be mistaken with rvalue references). Since the C++ standard uses the term forwarding reference, I will use that term in this article.
A forwarding reference is a special kind of reference that preserve the value category of a (function) argument making it possible to forward it to another function using std::forward.

For each of the three cases, consider the general form of invoking the template function:

Case 1: ParamType is a Reference or Pointer

In the first case, ParamType is a reference or pointer type, but not a forwarding reference. In this case, type deduction works like this:

  1. If expr evaluates to a reference, ignore the reference part.
  2. Then match expr‘s type against ParamType to determine T.

For example, if the function template is declared like this:

Then given the following variables:

Notice on lines 6, and 7 where expr is a const int or const int&, then T is deduced to be const int. The constness of expr becomes part of the type deduced for T.

If, on the other hand, ParamType is changed to const T& then type deduction works slightly differently:

Since the constness is now part of param‘s type, there is no need for const to be part of T‘s type deduction.

If param were a pointer or a pointer to const, then the type deduction for T works the same way:

This may seem obvious so far, but becomes less obvious if ParamType is a forwarding reference.

Case 2: ParamType is a Forwarding Reference

Now let’s consider the case where ParamType is a forwarding reference:

On line 4, an int variable (i) is defined. This is an lvalue (according to the value category rules defined at the beginning of this article). On line 9, i is passed to f. In this case, ParamType is deduced to int& (lvalue reference) and T is deduced to int& (lvalue reference).

On line 9, ci (lvalue) is passed to f and ParamType is deduced to const int& (lvalue reference) and T is deduced to const int& (lvalue reference). Similar deduction rules are applied to rci on line 10.

On line 11, the value 3 (prvalue, which is a primary value category of rvalue) is passed to f. In this case ParamType is deduced to int&& (rvalue reference) and T is deduced to int. The deduction rules for rvalues are the same as Case 1 above (expr‘s type is matched against ParamType to determine T).

The general rules of type deduction when ParamType is a forwarding reference, are:

  • If expr is an lvalue, both T and ParamType are deduced to be lvalue references.
  • If expr is a rvalue (prvalue or xvalue), then the rules for Case 1 are applied.

In short, use a forwarding reference when you need to maintain the value category of the template argument. This is almost always the case when the arguments are being forwarded to another function.

Case 3: ParamType is Neither a Pointer nor a Reference

If ParamType is neither a pointer nor a reference, then we say that the parameter is passed-by-value:

In this case, the rules for type deduction are:

  1. If expr‘s type is a reference, ignore the reference part
  2. If expr‘s type is const, ignore that too.

Then we have:

Note that despite ci and rci being const values, param doesn’t become const. Just becase expr can’t be modified doesn’t mean that a copy of it can’t be.

This pretty much summarizes the rules that are applied during template parameter type deduction. There are a few more cases that can be considered, for example how static arrays and function objects decay to their pointer types (see the decay type trait for more information). I encourage the reader to consult Scott Meyers’ books[7] for more information regarding the edge cases of template parameter type deductions. But at this point, you should have a good foundation for understanding the decltype specifier and std::declval which is the subject of the next sections.

decltype Specifier

The decltype specifier is used to inspect the declared type and value category of an expression.

Earlier, in the section about Function Templates, the decltype specifier was used to determine the return type for the max function template. If you read the warning that followed the code example, you might know that in certain cases, the compiler will generate a warning about returning a reference to a temporary. But under what circumstances does this happen?

In most cases, the decltype produces the expected type:

No surprises here. decltype gives the exact type as the provided expression maintaining const (and volatile), reference (&) and pointer (*) attributes.

When the the expression passed to decltype is parenthesized, then the expression is treated as an lvalue and decltype adds a reference to the expression:

Okay, but this doesn’t explain why the max function template can sometimes return a reference. To understand when this happens, we need to look at the deduction guide for the ternary (conditional) operator. The ternary operator has the form:

First, condition is evalutated and (implicitly converted) to bool. If the result is true, then expr1 is evaluated. If the result is false, then expr2 is evaluated. The deduction rules for the resulting value of the ternary operator are complex and you can read the full guide here.

One of the rules of the deduction guide for the ternary operator states that if expr1 and expr2 are glvalues (lvalues or xvalues) of the the same type and the same value category, then the result has the same type and value category.

Here is the max function template again:

So if T and U are the same type and value category (see Case 3 above when the template parameter is passed-by-value) then decltype(a > b ? a : b) will have the same type and value category of both a and b. If both a and b are the same type and they are always both lvalues (since they are identified by a name), then in order to avoid creating a temporary, decltype(a > b ? a : b) results in an lvalue references that refers to either a or b (whichever is larger).

Let’s take a look at a few examples of this:

On line 6, both a and b are ints and they are both lvalues. The result of the ternary operator is an lvalue reference.

On line 7, a is an int and c is a double. In this case, they are not the same type and the result of the ternary operator is the the type of the widest operand (in this case, double)

On line 8, both c and d are doubles and they are both lvalues. The result of the tenary operator is an lvalue reference.

On line 9, both operands are prvalues of type int. In this case, the result of the ternary operator is also a prvalue.

Consequently, the result of the ternary operator has an interesting side effect that you should be aware of. You can sometimes assign a value to the result of the ternary operator:

In the case where the the result of the ternary operator is an lvalue reference, you can actually assign a value to that result. In all other cases, the result of the ternary operator is a temporary prvalue that can’t be modified directly (unless it is stored in a lvalue first).

Okay, you’ve probably heard enough about the ternary operator. This should be enough knowledge to know under which circumstance the max function template returns a reference, but how can we fix this? In later sections, I’ll talk about using type traits to coerce declval to give us what we want. But before we get to type traits, there is one more tool we need in our template toolbox, and that’s the std::declval.

std::declval()

std::declval is a utility function that converts any type to a reference type without the need to create an instance of an object.

Okay, maybe that doesn’t help to understand why we need std::declval, so let’s take a look at an example. Suppose we have an abstract base class:

We know that Abstract is an abstract type because it declares at least one pure virtual function. Pure virtual functions are not required to (but may) have a definition. Classes with pure virtual functions are called abstract classes and cannot be instantiated.

Now suppose we wanted to determine the type that is returned from the Abstract::value method. As explained in the previous section, we can use the decltype keyword for this:

On line 10, we try to to determine the return type by constructing an instance of Abstract and inspect the return value of the value method. In this case, the compiler complains since, as was previously established, Abstract is an abstract class and can’t be instantiated (even in unevaluated expression like the decltype operator).

On line 11, we try to determine the return value by using a scope resolution operator (::). This only works if Abstract::value is a static function.

On line 12, the std::declval function template allows for the use of non-static member functions of abstract base classes, (or with types with deleted or private constructors, which is common when dealing with singletons) without requiring an instance of the type.

The std::declval utility function can be implemented like this:

We haven’t looked at type traits yet, but I think you can guess that std::add_rvalue_reference<T> makes T an rvalue reference.

You may have noticed that the declval function template only provides a declaration but not a definition. This is no mistake. This function does not have a definition! It simply converts T to an rvalue reference so that it can be used in an unevaluated context such as decltype.

std::declval converts any type (T) to a reference type to enable the use of member functions without the need to construct an instance of T.

Since std::declval is not defined and therefore, it can only be used in an unevaluated context such as decltype.

Type Traits

C++11 introduces the type_traits library.

Type traits defines a compile-time template-based interface to query or modify the properties of types.

Type traits allow you to discover certain things about a type at compile-time. Some things you may want to know about types are:

  1. Is it an integral type?
  2. Is it a floating-point type?
  3. Is it a class type?
  4. Is it a function type?
  5. Is it a pointer type?
  6. Are two types the same?
  7. Is one type derived from the other?
  8. Is one type convertible to another?

And the list goes on… There are many things we might want to know about one or more types that can be determined at compile-time.

Don’t confuse type traits with Run-Time Type Information (RTTI) which is used to query type information at run-time. Type traits are resolved at compile-time and impose no run-time overhead.

Type traits are the cornerstone for “Substitution Failure Is Not An Error” (SFINAE). But before we look at SFINAE, let’s investigate a few type traits that we can use as the basis for SFINAE later.

Keep in mind that a lot of the type traits described in following sections comes from the Standard Template Library (STL). You don’t need to define these types yourself in your own code. You can find the original source code for the type_traits library here:

My motivation for describing the type traits in this article is to give the reader a better understanding of how they work. Once you know how they work, you will have a better understanding of how to use them correctly.

integral_constant

The integral_constant structure is the base class for the type traits library. It is a wrapper for a static constant of a specified type. It wraps both the type and a value in a struct so it can be used as a type. You’ll see why this is useful later.

The integral_constant class (struct) template is composed of a type template parameter (T) and a non-type template parameter v (of type T).

The value type that was used to instantiate the integral_constant can be queried through the value_type type alias and the type of the integral_constant itself can be queried through the type type alias.

The operator value_type() member function defined on line 12 is an implicit conversion operator which allows an instance of the integral_constant template to be converted to value_type at compile-time. This allows an instance of integral_constant to be used in place where value_type is expected (in mathematical expressions for example).

The value_type operator() member function defined on line 17 is a function call operator that takes no parameters and returns value. This allows integral_constant to be used as a function object that takes no parameters and returns the stored value.

On lines 1 and 2, two type aliases of the integral_constant template are defined: three_t which is a type that represents 3, and five_t which is a type that represents 5.

On lines 4 and 5, two instances are instantiated using the type aliases that were just defined. three is an instance of type_constant<int, 3> and five is an instance of type_constant<int, 5>.

On line 7 and 8, two different methods to get the internal value are demonstrated. The first method on line 7 uses the function call operator to retrieve the internal value. On line 8, the implicit conversion operator is used to convert three and five to their integer equivalents to be multiplied together. Both expressions result in 15. If you run this program, the following is printed to the console:

3 * 5 = 15

That might be the most contrived method of computing the value 15 I’ve ever seen, but in the next section it will be become clear why this is useful.

bool_constant

With the definition of integral_constant from the previous section, other types can be derived from integral_constant. One useful type that can be derived from integral_constant is bool_constant. It is not necessary to define a full class for this as a template alias will do:

The bool_constant defines a “boolean” integral_constant. And as you may have guessed, we can define two new types based on bool_constant.

true_type

true_type is a type alias of bool_constant with a value of true:

false_type

false_type is a type alias of bool_constant with a value of false:

Both true_type and false_type are aliases of integral_type which means that they can be used wherever a class or struct type can be used. For example, we can create a partial specialization of a class that is derived from either true_type or false_type. But before I get into that, I need to introduce another useful tool for our type traits library: enable_if.

type_identity

At first glance, the type_identity class template may not seem very useful. It simply mimics the type T that was specified in the template argument.

The type_identity class template becomes useful in a non-deduced context (for example when used with the decltype specifier). We’ll use it later to help form SFINAE enabled types (see add_lvalue_reference and add_rvalue_reference).

void_t

void_t is an alias template that maps any number of type template parameters to void. This is useful in the context of SFINAE where you only want to check if a certain set of operations is valid on a type but you don’t care about the return type of that check. void_t allows you to form these expressions without concern for the return type.

The void_t alias template is commonly used to check if a certain operation is valid on a specific type. For example, if we want to check if a type supports the pre-increment operator, but we don’t care about the the actual result type, then we could do something like this:

In this example, the primary template for has_pre_increment_operator is derived from false_type. The primary template is only chosen if there isn’t a partial specialization that is a better match. In order to get the compiler to choose the specialized version of the template definition, the operation ++std::declval<&T>() must succeed. But since we only want to see if the operation succeeded, but we don’t care what the return value is, we can wrap the result of performing the pre-increment operator in the void_t alias template.

Since void_t takes a variadic number of template parameters, void_t can be used to check if any number of operations are valid on one or more types.

When the compiler fails to instantiate the second template argument in the specialized version of the has_pre_increment_operator, this is called substitution failure and is the basis of SFINAE (Substitution Failure Is Not An Error). SFINAE is used to express type trait operations and the void_t template alias is used to help formulate those expressions.

enable_if

The enable_if struct template provides a convenient mechanism to leverage SFINAE in our template classes. “Substitution Failure Is Not An Error” (or SFINAE) is a C++ technique to conditionally remove specific functions from overload resolution based on a type’s traits. We’ll get into more details of SFINAE later, for now let’s take a look at how we can define the enable_if template:

The primary template for enable_if is a class template that has two template parameters:

  1. bool B: A non-type template parameter that can be either true or false.
  2. class T: A type template parameter that can be any type. If no type is provided, void is used by default.

The magic trick comes from the partial specialization that is defined when B is true. The type type alias is not defined in the primary template and is only defined when B is true. Any attempt to access the type type alias when B is false will fail (since it’s just not defined in this case).

We’ll see how we can use this to leverage SFINAE later. Before we can do that, we’ll need some type trait templates to work with.

To simplify coding, we’ll also define a helper template alias for the enable_if template:

Now, instead of typing typename enable_if<B, T>::type, we only need to type enable_if_t<B, T>. As you can see, this saves us a lot of typing.

The enable_if_t template alias was introduced in C++14 but there is nothing stopping you from defining these kinds of template aliases in your C++11 code.

remove_const

Sometimes is it convenient to express a type without the const qualifier associated with it. The remove_const class template allows us to do just that:

The primary template for remove_const is only used when T is a non-const type. If T is const, then the partial specialization kicks-in to remove the const modifier from T.

And again, we’ll also define a helper template alias:

The remove_const class template only removes the topmost const qualifier from T.

remove_volatile

Similar to remove_const, the remove_volatile class template can be used to remove the volatile qualifier from a type:

And the helper template alias:

remove_cv

The remove_cv class template is used to remove the topmost const, volatile, or both qualifiers if present:

In this example, the remove_const and remove_volatile are combined on line 4 to remove both const and volatile qualifiers from T (if present).

remove_reference

The remove_reference class template is used to remove any reference (or rvalue references) from a type.

If T is a reference (or rvalue reference) type, then the remove_reference class template will strip off the reference from the type.

remove_cvref

Now we can combine remove_cv and remove_reference to remove const, volatile, and references from a type:

remove_extent

It might be useful to extract the type of an array. The remove_extent class template can be used to extract the type of an array. For example, if T is an (bounded or unbounded) array of type X, then remove_extent_t<T> evaluates to X.

The primary template (lines 1-5) is used if T is not an array type. If T is an unbounded array (lines 7-11) or a bounded array (lines 13-17), then remove_extent<T>::type is defined to be the type of the array with the extents removed.

If T is a multidimensional array, remove_extent<T> only removes the first dimension.

A short-hand for the remove_extent class template:

remove_all_extents

Since the remove_extent class template only removes the first dimension of multidimensional arrays, the remove_all_extents class template can be used to remove all of the extents of multidimensional arrays.

The primary template (lines 1-5) for the remove_all_extents class template looks similar to the remove_extent class template. The primary template is only used when T is not an array type or all of the extents have already been removed from the type.

If T is an unbounded (line 7-11) or bounded (line 13-17) array type, then the appropriate partial specialization is used. In this case, the first dimension is removed from T and the remove_all_extents class template is invoked recursively to remove the next extent. This process continues until the primary template is reached.

And the shorthand form:

remove_pointer

The remove_pointer class template can be used to remove the pointer from a type. Any const or volatile qualifiers added to the pointer are also removed.

Any const or volatile qualifiers added to the pointed-to type are not removed. For example, const int* becomes const int, but int* const becomes int and const int* const becomes const int. If you also want to remove the const or volatile qualifiers from the pointed-to type, then you must use the remove_cv class template as well.

The primary template (lines 1-5) is used if T is not a pointer type. Partial specializations (lines 7-29) are used if T is a (const or volatile qualified) pointer type.

And a short-hand version:

add_const

In some cases, you may want to add the const qualifier to a type. The add_const class template can be used to add the const qualifier to any type T (except for function and reference types, since these can’t be const qualified).

Due to const collapsing rules, if T is already const, then adding another const to T does not change the constness of T.

If we try to apply the add_const class template to a function type, then the compiler will generate a C4180 warning that applying a const to a function type does not make sense and has no meaning. #pragma warning(disable: 4180) causes this warning to be suppressed in Visual Studio.

add_volatile

Similar to add_const class template, the add_volatile class template can be used to add the volatile qualifier to a type (except for function and reference types, since these can’t be volatile qualified).

Similar to the add_const class template, the add_volatile class template, adds the volatile qualifier to a type (T). Adding the volatile qualifier if T is already volatile does not change T.

Similar to add_const, if T is a function type, then the compiler will generate a C4180 warning. #pragma warning(disable: 4180) suppresses this warning in Visual Studio.

add_cv

The add_cv template combines add_const and add_volatile.

Similar to add_const and add_volatile, we also need to suppress the C4180 compiler warning if we try to use add_cv with a function type.

add_lvalue_reference

The add_lvalue_reference class template is used to create an lvalue reference from a type. We have to be careful since trying to add a reference to non-referenceable type (for example, void is a non-referenceable type) will generate a compilation error. To account for this, we’ll use a helper template that is specialized for referenceable types. Trying to use add_lvalue_reference with a non-referenceable type should produce the original type.

The add_lvalue_reference_helper class template uses SFINAE to safely convert T to T&. If T is a referenceable type, then the partial specialization (lines 7-11) succeeds and add_lvalue_reference_helper<T>::type evaluates to T&. If the partial specialization fails, then T is a non-referenceable type, and the primary template is chosen. In this case, add_lvalue_reference_helper<T>::type evaluates to T.

On lines 13-15, the add_lvalue_reference class template is defined which is derived from add_lvalue_reference_helper allowing the add_lvalue_reference to be defined with a single template parameter. Theoretically, the add_lvalue_reference class template could be implemented without add_lvalue_reference_helper, but then we’d need to define add_lvalue_reference using two template parameters. Since the second template parameter is only used for SFINAE, using add_lvalue_reference_helper allows us to define the add_lvalue_reference class template using a single template parameter.

add_rvalue_reference

Similar to add_lvalue_reference, the add_rvalue_reference class template is used to create an rvalue reference from a type. Once again, we’ll use a helper class template to account for non-referenceable types (such as void).

The derivation for the add_rvalue_reference class template is similar to that of add_lvalue_reference, so I won’t repeat it here.

Due to reference collapsing rules, add_rvalue_reference<T&> will result in T& not T&&. There are two reference collapsing rules:
  1. An rvalue reference to an rvalue reference becomes becomes an rvalue reference.
  2. All other references to references (all combinations involving an lvalue reference) becomes an lvalue reference.

In the case where T is an lvalue reference, then add_rvalue_reference<T&> will collapse into a lvalue reference[8].

add_pointer

Similar to add_lvalue_reference and add_rvalue_reference class templates, the add_pointer class template adds a pointer to a give type T. If T is a reference type, then add_pointer<T>::type becomes remove_reference_t<T>*, that is, a pointer is added to the type T, after removing the reference.

Although it is possible to have a reference to a pointer (T*&), it is not possible to add a pointer to a reference (T&*). Attempting to create a pointer to a reference type will result in a compiler error.

Although it is possible to implement the add_pointer class template using a similar technique that is used to implement the add_lvalue_reference and add_rvalue_reference class templates, I want to demonstrate a different technique that utilizes SFINAE to achieve a similar result. Instead of using a struct with partial specialization, we declare two functions.

The first function declared on lines 1-2 takes an int parameter and returns the template argument T with the reference removed and a pointer added to the type wrapped in the type_identity template (which provides the type member).

If T a const, volatile, or reference-qualified function type, then trying to add a pointer to T (T*) would normally generate a compiler error. Instead of generating an error, the compiler will consider the second overload of add_pointer_helper instead.

The second function declared on lines 4-5 is a fallback function that takes any other parameter type using the ellipsis (...) and since the compiler considers this the worst possible match, it only chooses this overload if the compiler fails to instantiate the first version of the function. In this case, the template argument T is wrapped in the identity_type unmodified.

This technique that forces the compiler to choose worse match during function overload resolution is discussed in more detail later in the article (see SFINAE Out Function Overloads)

Both functions can’t take the same parameters (in this case int) because then the function signatures would become ambiguous and would fail to compile. Using the ellipsis (...) tells the compiler to only match this version of the function if it fails to instantiate the first version of the function.

You’ll notice that we provide a declaration of the add_pointer_helper functions but do not provide a definition. This is perfectly fine, as long as these functions are only used in a non-deduced context such as an expression that is only evaluated using the decltype specifier.

On lines 7-9, the add_pointer class template is defined and is derived from the return value of the add_pointer_helper function.

And, as usual, the short-hand alias of the add_pointer class template is defined on lines 11-12.

conditional

In some cases, it is useful to choose a specific type based on some condition. The conditional class template can be used to return one type or another type based on some condition (usually based on another type trait). The conditional class template is equivalent to an if condition in regular C++.

On lines 1-5 the primary template is defined. When B is true, then the type is an alias of T (let’s call this the true type). However, when the partial specialization where B is false is matched, then type is F (the false type).

Pretty simple right? Let’s see how we can use the conditional class template to implement more complex logical template types.

conjunction

A logical conjunction is a truth-functional operator that is true if and only if all of its operands are true. This is equivalent to a logical AND (&&) operation.

The conjunction class template works by passing any number of type traits that are derived from either true_type or false_type (or has a static member variable value that is convertible to bool) as template arguments to conjunction. The conjunction class template is derived from the first template argument whose value member variable is (or is convertible to) false. If all type traits passed to conjunction are true, then conjunction is derived from the last type trait in the template argument list.

We haven’t looked at many type trait templates that are derived from true_type or false_type yet, but these are introduced later in the article. For now, we just have to keep in mind that a class template that derives from either true_type or false_type has a static const member variable value that is true if it is derived from true_type or false if it is derived from false_type.

We’ll use a recursive variadic template to implement the conjunction class template. First, let’s take a look at the primary template.

The primary template is a class template that doesn’t specialize any of the template parameters. The compiler will choose this version of the conjunction class template only if there isn’t a specialization of the class template that is a better match. As we’ll see in a second, the primary template will only be chosen when conjunction is used without any template arguments (which is probably a mistake by the programmer). Although the primary template should never be chosen by the compiler, we need it before we can specialize it.

Now let’s take a look at the base case of the recursive variadic template, that is, when conjunction has only a single template argument.

In the base case, when conjunction has only a single template argument (T), then conjunction is derived from T. Since all of the template arguments passed to conjunction must have a static member variable called value that is convertible to bool (like true_type and false_type), then conjunction::value is true when T::value is true and false when T::value is false.

Now let’s look at the recursive case:

The recursive case is used when conjunction has two or more template arguments. In this case, the conditional class template is used to conditionally select either conjunction<Tn...> (recursively calling itself) if T::value is true or T when T::value is false.

Consequently, if T::value is false then the expansion of Tn... stops and no further types need to be instantiated to determine the type of conjunction. This is in contrast to using fold expressions (... && Tn::value) where every T in Tn is instantiated before the expression is evaluated.

In order to reduce some typing later, we’ll also define an inline variable template for conjunction::value:

Now, instead of typing conjunction<...>::value, we only need to type conjunction_v<...>. Small victory, but every little bit helps.

Variable templates require a C++14 compatible compiler. Inline variables requires a C++17 compiler. The inline specifier can be dropped if you need to support C++14.

In my own projects, I use the following macro to support both C++14 and C++17 when available:

Then prepend inline variable templates with the INLINE_VAR macro instead of inline constexpr.

disjunction

Similar to conjunction, the disjunction class template is equivalent to a logical OR operator.

The disjunction class template works by passing any number of type traits that are derived from either true_type or false_type (or has a static member variable value that is convertible to bool) as template arguments. The disjunction class template is derived from the first template argument whose value member variable is (or is convertible) to true. If all template arguments passed to disjunction are false, then disjunction is derived from the last type trait in the template argument list.

Similar to the implementation of the conjunction class template, we’ll use a recursive variadic template to implement the disjunction class template. First, let’s take a look at the primary template.

The primary template doesn’t specialize any of the template parameters. The compiler will use the primary template only if there isn’t a specialization of the disjunction class template that is a better match. Since there is a specialization for a single template argument and a specialization for two or more template arguments (see below), the primary template is only chosen when disjunction is used without any template arguments (which is probably a mistake). Although the primary template should never be chosen by the compiler, we need to define it before we can specialize it.

The base case for the recursive variadic template has only a single template parameter:

In the base case, when the disjunction class template has only a single template argument (T), then disjunction is derived from T. Since all of the template arguments passed to disjunction must have a static member variable called value that is convertible to bool, then disjunction::value is true when T::value is true and false if T::value is false.

Now, let’s look at the recursive case.

The recursive case is instantiated when disjunction is used with two or more template arguments. In this case, the conditional class template is used to conditionally select either T if T::value is true or disjunction<Tn...> (recursively calling itself) if T::value is false.

Consequently, if T::value is true, then the expansion of Tn... stops and no further types need to be instantiated to determine the type of disjunction. This is in contrast to using the fold expression (... || Tn::value) which must instantiate every T in Tn before the expression is evaluated.

And similar to conjunction, we’ll define an inline variable template to create a short-hand for disjunction::value

Now, instead of typing disjunction<...>::value, we only need to type disjunction_v<...>.

negation

The negation class template forms a logical negation of the type trait T.

The negation class template is derived from the bool_constant class template. If T::value is true, then negation::value is false (which is equivalent to being derived from false_type) and if T::value is false, then negation::value is true (which is equivalent to being derived from true_type).

And a short-hand version of negation::value:

With the definition of conditional, conjunction, disjunction, and negation, we have the logical operators that are needed to make any logical combination of type traits that we need. In the following sections, we’ll use these logical class templates as the basis for other type traits.

is_same

The is_same class template is the first class in our type traits library that can actually be considered a type trait. Most type traits result in a boolean value that is either true or false. This is accomplished by inheriting from either true_type if the type trait is true or false_type if the type trait is false.

In most cases, the primary template for the type trait is derived from false_type and one or more partial specializations exist that are derived from true_type.

First, let’s look at the primary template for the is_same type trait.

The primary template is chosen by the compiler when T and U are different types. Next, we’ll create a partial specialization of is_same when T and U are the same types.

The partial specialization is only chosen when T and U are exactly the same types. That means that their const, volatile, reference or pointer attributes must also be the same. If you want to ignore any const, volatile, or references that might adorn the type, then wrap the type in either the remove_cv or remove_cvref class template.

A short-hand for is_same::value can be defined using a variable template (requires C++14):

is_void

With the is_same and remove_cv type traits defined, we can use these to define other type traits which can be used to identify all of the primary types. The is_void type trait evaluates to true_type if the template argument is void (ignoring any const and volatile qualifiers) and false_type otherwise.

As was shown in the previous section, the is_same type trait is derived from true_type when the first and second template arguments are exactly the same type, and false_type otherwise. We can use this to define a type trait that checks for a primary type (like ints and floats which we’ll look at in the following sections).

We’ll also define an inline variable template called is_void_v that can be used as a short-hand for is_void::value:

is_null_pointer

Similar to the is_void type trait, the is_null_pointer type trait is derived from true_type if the template argument is std::nullptr_t.

The implementation of is_null_pointer is similar to is_void.

is_any_of

The is_any_of type trait can be used to check if a certain type template argument matches any one of the other type template arguments. We’ll use the disjunction and the is_same type traits defined earlier to implement the is_any_of type trait.

The is_any_of type trait does not currently exist in the C++ standard, but we’ll use this type trait to simplify the defintion of other type traits later in this article.

The implementation of the is_any_of type trait is super simple since we can rely on the disjunction and the is_same type traits that were previously defined. Variadic template arguments are used to check if the type T matches any one of types in the pack represented by Tn.

is_integral

With the is_any_of type trait defined previously, it is now super simple to implement other type traits. The is_integral type trait is true_type if the template argument is one of the following types:

Or any other equivalent type including signed or unsigned, with const, and volatile qualified variants. Otherwise, it is false_type.

Using the is_any_of type trait defined previously, the is_intgral type trait becomes much simpiler. If the template argument T is any one of the types listed (after removing const and volatile qualifiers), then is_integral is true_type, otherwise it is false_type.

is_floating_point

Similar to the is_integral type trait, the is_floating_point type trait is true_type if the template argument is one of the following types:

Including const and volatile qualified variants. Otherwise, it is false_type.

is_arithmetic

The is_arithmetic type trait is true_type if its template argument is either an integral type or a floating-point type, and false_type otherwise. As you may have guessed, we can use the is_integral and is_floating_point type traits defined earlier in combination with disjunction to implement this type trait.

An an alternative and equivalent implementation of the is_arithmetic type trait uses the bool_constant class template directly:

Although it doesn’t change the meaning of the is_arithmetic type trait, it demonstrates that you can use the logical OR operator (||) to evaluate template arguments. Note however that the disjunction class template expects its template arguments to be types, while the bool_constant class template expects a non-type template argument that is convertible to bool. In this case, we can use the _v short-hand variants of the type traits to get the value member variable of the type trait.

is_const

The is_const type trait checks to see if a type is const-qualified. The is_const type trait is derived from true_type if the type is const-qualified, or false_type otherwise.

Similar to the remove_const class template that was previously shown, the is_const type trait uses partial specialization to detect const-qualified types.

is_reference

The is_reference type trait can be used to check if a type is either an lvalue reference or rvalue reference type.

The is_reference type trait is derived from true_type if the type T is either an lvalue (line 6) or an rvalue (line 10) reference. Otherwise, is_reference is derived from false_type.

And the short-hand version:

is_bounded_array

The is_bounded_array type trait checks if T is an array type with a known size.

If T is a bounded array (an array with a known size in the form of T[N]), then is_bounded_array is derived from true_type, otherwise it is derived from false_type.

And the short-hand variant:

is_unbounded_array

The type trait for an unbounded array is very similar to that of the bounded array. The primary difference is that the size of the array is unspecified.

is_array

The is_array type trait can be used to check if a type is either a bounded or unbounded array.

is_function

The is_function type trait can be used to check if a type is a function. The is_function type trait only considers free functions and static member functions of a class to be function types. std::function, lambdas, callable function objects (classes or structs with overloaded function call operator operator()) and pointers to functions are not considered function types.

The is_function type trait works on the basis that only function types and reference types can’t be const-qualified. Adding the const qualifier to a function type does not change its constness. If T is a function type (or a reference type), then is_const_v<const T> will be false. If is_reference_v<T> is also false, then T must be a function type.

In fact, trying to add a const qualifier to a function type will generate a warning (C4180) in Visual Studio. The warning about applying a const qualifier to a function type can be disabled using the #pragma warning(disable: 4180) pragma as shown in the code snippet.

decay

The decay class template is used to perform the same conversion operations that are applied to template arguments when passed by value. The decay class template will do one of three things depending on the argument type:

  • Array: If T is an array of type U (either an unbounded array of the form U[] or a bounded array of the form U[N]) then decay<T>::type will be U*. That is, array types decay to pointers to the array element type.
  • Function: If T is a function type or a reference to a function, then decay<T>::type will be add_pointer_t<T>. That is, function types become pointers to functions.
  • Neither array nor function: decay<T>::type removes any reference, const, and volatile qualifiers from the type using remove_cv_t<remove_reference_t<T>>

We’ll use a piecewise technique to implement the decay class template that is split into 3 parts:

  1. The primary template which is used when T is neither an array type nor a function type.
  2. A partial specialization when T is an array type.
  3. A partial specialization when T is a function type.

Similar to how we implemented add_lvalue_reference, and add_rvalue_reference, we’ll use a helper class template so that the decay class template only requires a single template parameter.

First, we’ll look at the primary template for the decay_helper class template.

The primary template for the decay_helper class template takes three template arguments:

  1. T: The type to decay,
  2. IsArray: A boolean non-type template parameter that is true if T is an array type or false otherwise.
  3. IsFunc: A boolean non-type template parameter that is true if T is a function type or false otherwise.

The IsArray template parameter defaults to is_array_v<T> and the IsFunc template parameter defaults to is_function_v<T>.

Since we will provide a partial specialization when T is an array type and another specialization for when T is a function type, the primary template is only used when T is neither an array nor a function type (that is, both IsArray and IsFunc are false).

When T is neither an array nor a function type, we’ll use the remove_cv class template to remove any const, and volatile qualifiers from T.

Next, we’ll provide a partial specialization of decay_helper when T is an array type.

This partial specialization will be used when is_array_v<T> evaluates to true and is_function_v<T> evaluates to false. In this case, the resulting type is remove_extent_t<T>*, which removes the array extent from the type and adds a pointer to the resulting element type.

Next, we’ll provide a partial specialization of decay_helper when T is a function type.

This partial specialization is used when is_array_v<T> evaluates to false and is_function_v<T> evaluates to true. In this case, we just add a pointer to the function type using the add_pointer class template.

With all the possible combinations handled, we can now define the decay class template.

The decay class template uses the decay_helper class template to determine the type of the type member after removing any references from T.

And of course, on lines 28-29, the short-hand alias template for typename decay<T>::type is defined.

SFINAE

SFINAE (sfee-nay) is an acronym for “Substitution Failure Is Not An Error” and it refers to a technique used by the compiler to turn potential errors into “deduction failures” during template argument deduction. It is a technique used to eliminate certain functions from being chosen during overload resolution or to choose a particular class template specialization based on characteristics of the template arguments. If the compiler finds an invalid expression during template argument deduction, instead of generating a compiler error, it removes that function or class specialization from the set of possible candidates.

We’ve already seen a few examples of using SFINAE to generate a few of the type traits in the previous sections. The add_pointer type trait uses a set of function overloads to SFINAE-out the case where adding a pointer to T would result in an error. For example, if T was a const, volatile or reference qualified function type, then attempting to add a pointer to T would generate a compile-time error. Instead of generating an error, the compiler eliminates the first function from the set of overloads and considers the next function.

Another SFINAE technique is used to define the add_lvalue_reference and add_rvalue_reference type traits. Instead of function overloads, partial specialization of a class template is used to SFINAE-out cases where adding a reference to T would result in a compiler error (for example, if T is void).

SFINAE-Out Function Overloads

To demonstrate SFINAE using function template overloads, we’ll create a SFINAE-based type trait to determine if a type T is constructible using a particular set of arguments (Args...).

The approach to implementing SFINAE-based traits with function overloads is to declare two overloaded function templates named test (you can use any name for this function, but test is traditionally used for SFINAE-based traits).