Appendix D

Standard Type Utilities

The C++ standard library largely consists of templates, many of which rely on various techniques introduced and discussed in this book. For this reason, a couple of techniques were “standardized” in the sense that the standard library defines several templates to implement libraries with generic code. These type utilities (type traits and other helpers) are listed and explained here in this chapter.

Note that some type traits need compiler support, while others can just be implemented in the library using existing in-language features (we discuss some of them in Chapter 19).

D.1 Using Type Traits

When using type traits, in general you have to include the header file <type_traits>:

#include <type_traits>

Then the usage depends on whether a trait yields a type or a value:

• For traits yielding a type, you can access the type as follows:

typename std::trait<>::type
std::trait_t<>              //since C++14

• For traits yielding a value, you can access the value as follows:

std::trait<>::value
std::trait<>()        //implicit conversion to its type
std::trait_v<>        //since C++17

For example:

utils/traits1.cpp

#include <type_traits>
#include <iostream>
int main()
{
   int i = 42;
   std::add_const<int>::type c = i;        // c is int const
   std::add_const_t<int> c14 = i;        // since C++14
   static_assert(std::is_const<decltype(c)>::value, "c should be const");

   std::cout << std::boolalpha;
   std::cout << std::is_same<decltype(c), int const>::value
  //true
             <<\n;
   std::cout << td::is_same_v<decltype(c), int const>        //since C++17
             << \n;
   if (std::is_same<decltype(c), int const>{}) {  //implicit conversion to bool
     std::cout
<< "same \n";
   }
}

See Section 2.8 on page 40 for the way the _t version of the traits is defined. See Section 5.6 on page 83 for the way the _v version of the traits is defined.

D.1.1 std::integral_constant and std::bool_constant

All standard type traits yielding a value are derived from an instance of the helper class template std::integral_constant:

namespace std {
  template<typename T, T val>
  struct integral_constant {
    static constexpr T value = val;                 // value of the trait
    using value_type = T;                           // type of the value
    using type = integral_constant<T,val>;
    constexpr operator value_type() const noexcept {
      return value;
    }
    constexpr value_type operator() () const noexcept {  //since C++14
      return value;
    }
  };
}

That is:

• We can use the value_type member to query the type of the result. Since many traits yielding a value are predicates, value_type is often just bool.

• Objects of traits types have an implicit type conversion to the type of the value produced by the type trait.

• In C++14 (and later), objects of type traits are also function objects (functors), where a “function call” yields their value.

• The type member just yields the underlying integral_constant instance.

If traits yield Boolean values, they can also use1

namespace std {
    template<bool B>
    using bool_constant = integral_constant<bool, B>;  // since C++17
    using true_type = bool_constant<true>;
    using false_type = bool_constant<false>;
}

so that these Boolean traits inherit from std::true_type if a specific property applies and from std::false_type if not. That also means that their corresponding value members equal true or false. Having distinct types for the resulting values true and false allows us to tag-dispatch based on the result of type traits (see Section 19.3.3 on page 411 and Section 20.2 on page 467).

For example:

utils/traits2.cpp

#include <type_traits>
#include <iostream>

int main()
{
   using namespace std;
   cout << boolalpha;

   using MyType = int;
   cout << is_const<MyType>::value << ’\n’;   //prints false

   using VT = is_const<MyType>::value_type;   // bool
   using T = is_const<MyType>::type;          // integral_constant<bool, false>
   cout << is_same<VT,bool>::value << ’\n’;   //prints true
   cout << is_same<T, integral_constant<bool, false>>::value
        <<’\n’;                               //prints true
   cout << is_same<T, bool_constant<false>>::value
        <<’\n’;                               //prints true (not valid
                                              // prior to C++17)

   auto ic = is_const<MyType>();              // object of trait type
   cout << is_same<decltype(ic), is_const<int>>::value << ’\n’; // true
   cout << ic() << ’\n’;           //function call (prints false)

   static constexpr auto mytypeIsConst = is_const<MyType>{};
   if constexpr(mytypeIsConst)  {     //compile-time check since C++17 => false
     …                              //discarded statement
   }
   static_assert(!std::is_const<MyType>{}, "MyType should not be const");
}

Having distinct types for non-Boolean integral_constant specializations is also useful in various metaprogramming contexts. See the discussion of the similar type CTValue in Section 24.3 on page 566 and its use for element access of tuples in Section 25.6 on page 599.

D.1.2 Things You Should Know When Using Traits

There are a few things to note when using traits:

• Type traits apply directly to types, but decltype allows us to also test the properties of expressions, variables, and functions. Recall, however, that decltype produces the type of a variable or function only if the entity is named with no extraneous parentheses; for any other expression, it yields a type that also reflects the type category of the expression. For example:

void foo (std::string&& s)
{
  // check the type of s:
  std::is_lvalue_reference<decltype(s)>::value        //false
  std::is_rvalue_reference<decltype(s)>::value        //true, as declared
  // check the value category of s used as expression:
  std::is_lvalue_reference<decltype((s))>::value      //true, s used as lvalue
  std::is_rvalue_reference<decltype((s))>::value      //false
}

See Section 15.10.2 on page 298 for details.

• Some traits may have nonintuitive behavior for the novice programmer. See Section 11.2.1 on page 164 for examples.

• Some traits have requirements or preconditions. Violating these preconditions results in undefined behavior.2 See Section 11.2.1 on page 164 for some examples.

• Many traits require complete types (see Section 10.3.1 on page 154). To be able to use them for incomplete types, we can sometimes introduce templates to defer their evaluation (see Section 11.5 on page 171 for details).

• Sometimes the logical operators &&, ||, and ! cannot be used to define new type traits based on other type traits. In addition, dealing with traits that might fail can become a problem or at least cause some drawbacks. For this reason, special traits are provided that allow us to logically combine Boolean traits. See Section D.6 on page 734 for details.

• Although the standard alias templates (ending with _t or _v) are often convenient, they also have downsides, making them unusable in some metaprogramming contexts. See Section 19.7.3 on page 446 for details.

D.2 Primary and Composite Type Categories

We start with the standard traits that test primary and composite type categories (see Figure D.1).3 In general, each type belongs to exactly one primary type category (the white elements in Figure D.1). Composite type categories then merge primary type categories into higher-level concepts.

Image

Figure D.1. Primary and Composite Type Categories

D.2.1 Testing for the Primary Type Category

This section describes type utilities that test the primary type category of a given type. For any given type, exactly one of the primary type categories has a static value member that evaluates to true.4 The result is independent of whether the type is qualified with const and/or volatile (cv-qualified).

Note that for types std::size_t and std::ptrdiff_t, is_integral<> yields true. For type std::max_align_t, which one of these primary type categories yields true is an implementation detail (thus, it might be an integral or floating-point or class type). The language specifies that the type of a lambda expression is a class type (see Section 15.10.6 on page 310). Applying is_class to that type therefore yields true.

Trait Effect

is_void<T>

Type void

is_integral<T>

Integral type (including bool, char, char16_t, char32_t, wchar_t)

is_floating_point<T>

Floating-point type (float, double, long double)

is_array<T>

Ordinary array type (not type std::array)

is_pointer<T>

Pointer type (including function pointer but not pointer to non-static member)

is_null_pointer<T>

Type of nullptr (since C++14)

is_member_object_pointer<T>

Pointer to a nonstatic data member

is_member_function_pointer<T>

Pointer to a nonstatic member function

is_lvalue_reference<T>

Lvalue reference

is_rvalue_reference<T>

Rvalue reference

is_enum<T>

Enumeration type

is_class<T>

Class/struct or lambda type but not a union type

is_union<T>

Union type

is_function<T>

Function type

Table D.1. Traits to Check the Primary Type Category

std:: is_void < T >::value

• Yields true if type T is (cv-qualified) void.

• For example:

is_void_v<void>           // yields true
is_void_v<void const      // yields true
is_void_v<int>            // yields false
void f();
is_void_v<decltype(f)>    // yields false (f has function type)
is_void_v<decltype(f())>  // yields true (return type of f() is void)

std:: is_integral < T >::value

• Yields true if type T is one of the following (cv-qualified) types:

bool

– a character type (char, signed char, unsigned char, char16_t, char32_t, or wchar_t)

– an integer type (signed or unsigned variants of short, int, long, or long long; this includes std::size_t and std::ptrdiff_t)

std:: is_floating_point < T >::value

• Yields true if type T is (cv-qualified) float, double, or long double.

std:: is_array < T >::value

• Yields true if type T is a (cv-qualified) array type.

• Recall that a parameter declared as an array (with or without length) by language rules really has a pointer type.

• Note that class std::array<> is not an array type, but a class type.

• For example:

is_array_v<int[]>          // yields true
is_array_v<int[5]>         // yields true
is_array_v<int*>           // yields false

void foo(int a[], int b[5], int* c)
{
is_array_v<decltype(a)>    // yields false (a has type int*)
is_array_v<decltype(b)>    // yields false (b has type int*)
is_array_v<decltype(c)>    // yields false (c has type int*)
}

• See Section 19.8.2 on page 453 for implementation details.

std:: is_pointer < T >::value

• Yields true if type T is a (cv-qualified) pointer.

This includes:

– pointers to static/global (member) functions

– parameters declared as arrays (with or without length) or function types

This does not include:

– pointer-to-member types (e.g., the type of &X::m where X is a class type and m is a nonstatic member function or a nonstatic data member)

– the type of nullptr, std::nullptr_t

• For example:

is_pointer_v<int>                  // yields false
is_pointer_v<int*>                 // yields true
is_pointer_v<int* const>           // yields true
is_pointer_v<int*&>                // yields false
is_pointer_v<decltype(nullptr)>    // yields false

int* foo(int a[5], void(f)())
{
  is_pointer_v<decltype(a)>          // yields true (a has type int*)
  is_pointer_v<decltype(f)>          // yields true (f has type void(*)())
  is_pointer_v<decltype(foo)>        // yields false
  is_pointer_v<decltype(&foo)>       // yields true
  is_pointer_v<decltype(foo(a,f))>   // yields true (for return type int*)
}

• See Section 19.8.2 on page 451 for implementation details.

std:: is_null_pointer < T >::value

• Yields true if type T is the (cv-qualified) std:: nullptr_t, which is the type of nullptr.

• For example:

is_null_pointer_v<decltype(nullptr)>   // yields true

void* p = nullptr;
is_null_pointer_v<decltype(p)>        // yields false (p has not type std::nullptr_t)

• Provided since C++14.

std:: is_member_object_pointer < T >::value

std:: is_member_function_pointer < T >::value

• Yields true if type T is a (cv-qualified) pointer-to-member type (e.g., int X::* or int (X::*)() for some class type X).

std:: is_lvalue_reference < T >::value

std:: is_rvalue_reference < T >::value

• Yields true if type T is a (cv-qualified) lvalue or rvalue reference type, respectively.

• For example:

is_lvalue_reference_v<int>      // yields false
is_lvalue_reference_v<int&>      // yields true
is_lvalue_reference_v<int&&>      // yields false
is_lvalue_reference_v<void>      // yields false
is_rvalue_reference_v<int>      // yields false
is_rvalue_reference_v<int&>      // yields false
is_rvalue_reference_v<int&&>      // yields true
is_rvalue_reference_v<void>      // yields false

• See Section 19.8.2 on page 452 for implementation details.

std:: is_enum < T >::value

• Yields true if type T is a (cv-qualified) enumeration type. This applies to both scoped and un-scoped enumeration types.

• See Section 19.8.5 on page 457 for implementation details.

std:: is_class < T >::value

• Yields true if type T is a (cv-qualified) class type declared with class or struct, including such a type generated from instantiating a class template. Note that the language guarantees that the type of a lambda expression is a class type (see Section 15.10.6 on page 310).

• Yields false for unions, scoped enumeration type (despite being declared with enum class), std::nullptr_t, and any other type.

• For example:

is_class_v<int>                 // yields false
is_class_v<std::string>         // yields true
is_class_v<std::string const>   // yields true
is_class_v<std::string&>        // yields false
auto l1 = []{};
is_class_v<decltype(l1)>        // yields true (a lambda is a class object)

• See Section 19.8.4 on page 456 for implementation details.

std:: is_union < T >::value

• Yields true if type T is a (cv-qualified) union, including a union generated from a class template that is a union template.

std:: is_function < T >::value

• Yields true if type T is a (cv-qualified) function type. Yields false for a function pointer type, the type of a lambda expression, and any other type.

• Recall that a parameter declared as an function type by language rules really has a pointer type.

• For example:

void foo(void(f)())
{
is_function_v<decltype(f)>        // yields false (f has type void(*)())
is_function_v<decltype(foo)>      // yields true
is_function_v<decltype(&foo)>     // yields false
is_function_v<decltype(foo(f))>   // yields false (for return type)
}

• See Section 19.8.3 on page 454 for implementation details.

D.2.2 Test for Composite Type Categories

The following type utilities determine whether a type belongs to a more general type category that is the union of some primary type categories. The composite type categories do not form a strict partition: A type may belong to multiple composite type categories (e.g., a pointer type is both a scalar type and a compound type). Again, cv-qualifiers (const and volatile) do not matter in classifying a type.

std:: is_reference < T >::value

• Yields true if type T is a reference type.

• Same as: is_lvalue_reference_v<T> || is_rvalue_reference_v<T>

• See Section 19.8.2 on page 452 for implementation details.

Trait Effect

is_reference<T>

Lvalue or rvalue reference

is_member_pointer<T>

Pointer to nonstatic member

is_arithmetic<T>

Integral (including bool and characters) or floating-point type

is_fundamental<T>

void, integral (including bool and characters), floating-point, or std::nullptr_t

is_scalar<T>

Integral (including bool and characters), floating-point, enumeration, pointer, pointer-to-member, and std::nullptr_t

is_object<T>

Any type except void, function, or reference

is_compound<T>

The opposite of is_fundamental<T >: array, enumeration, union, class, function, reference, pointer, or pointer-to-member

Table D.2. Traits to Check for Composite Type Category

std:: is_member_pointer < T >::value

• Yields true if type T is any pointer-to-member type.

• Same as: ! (is_member_object_pointer_v<T> || is_member_function_pointer_v<T>)

std:: is_arithmetic < T >::value

• Yields true if type T is an arithmetic type (bool, character type, integer type, or floating-point type).

• Same as: is_integral_v<T> || is_floating_point_v<T>

std:: is_fundamental < T >::value

• Yields true if type T is a fundamental type (arithmetic type or void or std::nullptr_t).

• Same as: is_arithmetic_v<T> || is_void_v<T> || is_null_pointer_v<T>

• Same as: ! is_compound_v<T>

• See IsFundaT in Section 19.8.1 on page 448 for implementation details.

std:: is_scalar < T >::value

• Yields true if type T is a “scalar” type.

• Same as: is_arithmetic_v<T> || is_enum_v<T> || is_pointer_v<T>

|| is_member_pointer_v<T> || is_null_pointer_v<T>>

std:: is_object < T >::value

• Yields true if type T describes the type of an object.

• Same as: is_scalar_v<T> || is_array_v<T> || is_class_v<T> || is_union_v<T>)

• Same as: ! (is_function_v<T> || is_reference_v<T> || is_void_v<T>)

std:: is_compound < T >::value

• Yields true if type T is a type compound out of other types.

• Same as: !is_fundamental_v<T>

• Same as: is_enum_v<T> || is_array_v<T> || is_class_v<T> || is_union_v<T>

|| is_reference_v<T> || is_pointer_v<T> || is_member_pointer_v<T>

|| is_function_v<T>

D.3 Type Properties and Operations

The next group of traits tests other properties of single types as well as certain operations (e.g., value swapping) that may apply to them.

D.3.1 Other Type Properties

std:: is_signed < T >::value

• Yields true if T is a signed arithmetic type (i.e., an arithmetic type that includes negative value representations; this includes types like (signed) int, float).

• For type bool, it yields false.

• For type char, it is implementation defined whether it yields true or false.

• For all nonarithmetic types (including enumeration types) is_signed yields false.

std:: is_unsigned < T >::value

• Yields true if T is an unsigned arithmetic type (i.e., an arithmetic type that does not include negative value representations; this includes types like unsigned int and bool).

• For type char, it is implementation defined whether it yields true or false.

• For all nonarithmetic types (including enumeration types) is_unsigned yields false.

std:: is_const < T >::value

• Yields true if the type is const-qualified.

• Note that a const pointer has a const-qualified type, whereas a non-const pointer or a reference to a const type is not const-qualified. For example:

is_const<int* const>::value      // true
is_const<int const*>::value      // false
is_const<int const&>::value      // false

• The language defines arrays to be const-qualified if the element type is const-qualified.5 For example:

is_const<int[3]>::value          // false
is_const<int const[3]>::value    // true
is_const<int[]>::value           // false
is_const<int const[]>::value     // true

Trait Effect

is_signed<T>

Signed arithmetic type

is_unsigned<T >

Unsigned arithmetic type

is_const<T >

const-qualified

is_volatile<T >

volatile-qualified

is_aggregate<T >

Aggregate type (since C++17)

is_trivial<T >

Scalar, trivial class, or arrays of these types

is_trivially_copyable<T >

Scalar, trivially copyable class, or arrays of these types

is_standard_layout<T >

Scalar, standard layout class, or arrays of these types

is_pod<T >

Plain old data type (type where memcpy() works to copy objects)

is_literal_type<T >

Scalar, reference, class, or arrays of these types (deprecated since C++17)

is_empty<T >

Class with no members, virtual member functions, or virtual base classes

is_polymorphic<T >

Class with a (derived) virtual member function

is_abstract<T >

Abstract class (at least one pure virtual function)

is_final<T >

Final class (a class not allowed to derive from, since C++14)

has_virtual_destructor<T >

Class with virtual destructor

has_unique_object_representations<T>

Any two object with same value have same representation in memory (since C++17)

alignment_of<T >

Equivalent to alignof(T)

rank<T >

Number of dimensions of an array type (or 0)

extent<T,I=0 >

Extent of dimension I (or 0)

underlying_type<T >

Underlying type of an enumeration type

is_invocable<T,Args… >

Can be used as callable for Args… (since C++17) is_nothrow_invocable<T,Args…> Can be used as callable for Args… without throwing (since C++17)

is_invocable_r<RT,T,Args… >

Can be used as callable for Args… returning RT (since C++17) is_nothrow_invocable_r<RT,T,Args…> Can be used as callable for Args… returning RT without throwing (since C++17)

invoke_result<T,Args…>

Result type if used as callable for Args… (since C++17)

result_of<F,ArgTypes >

Result type if calling F with argument types ArgTypes (deprecated since C++17)

Table D.3. Traits to Test Simple Type Properties

std:: is_volatile < T >::value

• Yields true if the type is volatile-qualified.

• Note that a volatile pointer has a volatile-qualified type, whereas a non-volatile pointer or a reference to a volatile type is not volatile-qualified. For example:

is_volatile<int* volatile>::value      // true
is_volatile<int volatile*>::value      // false
is_volatile<int volatile&>::value      // false

• The language defines arrays to be volatile-qualified if the element type is volatile-qualified.6

For example:

is_volatile<int[3]>::value             // false
is_volatile<int volatile[3]>::value    // true
is_volatile<int[]>::value              // false
is_volatile<int volatile[]>::value     // true

std:: is_aggregate < T >::value

• Yields true if T is an aggregate type (either an array or a class/struct/union that has no user-defined, explicit, or inherited constructors, no private or protected nonstatic data members, no virtual functions, and no virtual, private, or protected base classes).7

• Helps to find out whether list initialization is required. For example:

template<typename Coll, typename… T>
void insert(Coll& coll, T&&… val)
{
 if constexpr(!std::is_aggregate_v<typename Coll::value_type>)  {
  coll.emplace_back(std::forward<T>(val)…);      // invalid for aggregates
 }
 else  {
  coll.emplace_back(typename Coll::value_type{std::forward<T>(val)…});
 }
}

• Requires that the given type is either complete (see Section 10.3.1 on page 154) or (cv-qualified) void.

• Available since C++17.

std:: is_trivial < T >::value

• Yields true if the type is a “trivial” type:

– a scalar type (integral, float, enum, pointer; see is_scalar() on page 707)

– a trivial class type (a class that has no virtual functions, no virtual base classes, no (indirectly) user-defined default constructor, copy/move constructor, copy/move assignment operator, or destructor, no initializer for nonstatic data members, no volatile members, and no nontrival members)

– an array of such types

– and cv-qualified versions of these types

• Yields true if is_trivially_copyable_v<T> yields true and a trivial default constructor exists.

• Requires that the given type is either complete (see Section 10.3.1 on page 154) or (cv-qualified) void.

std:: is_trivially_copyable < T >::value

• Yields true if the type is a “trivially copyable” type:

– a scalar type (integral, float, enum, pointer; see is_scalar<> on page 707)

– a trivial class type (a class that has no virtual functions, no virtual base classes, no (indirectly) user-defined default constructor, copy/move constructor, copy/move assignment operator, or destructor, no initializer for nonstatic data members, no volatile members, and no nontrival members)

– an array of such types – and cv-qualified versions of these types

• Yields the same as is_trivial_v<T> except it can produce true for a class type without a trivial default constructor.

• In contrast to is_standard_layout<>, volatile members are not allowed, references are allowed, members might have different access, and members might be distributed among different (base) classes.

• Requires that the given type is either complete (see Section 10.3.1 on page 154) or (cv-qualified) void.

std:: is_standard_layout < T >::value

• Yields true if the type has a standard layout, which, for example, makes it easier to exchange values of this type with other languages.

– a scalar type (integral, float, enum, pointer; see is_scalar<> on page 707)

– a standard-layout class type (no virtual functions, no virtual base classes, no nonstatic reference members, all nonstatic members are in the same (base) class defined with the same access, all members are also standard-layout types)

– an array of such types

– and cv-qualified versions of these types

• In contrast to is_trivial<>, volatile members are allowed, references are not allowed, members might not have different access, and members might not be distributed among different (base) classes.

• Requires that the given type (for arrays, the basic type) is either complete (see Section 10.3.1 on page 154) or (cv-qualified) void.

std:: is_pod < T >::value

• Yields true if T is a plain old datatype (POD).

• Objects of such types can be copied by copying the underlying storage (e.g., using memcpy()).

• Same as: is_trivial_t<T> && is_standard_layout_v<T>

• Yields false for:

– classes that don’t have a trivial default constructor, copy/move constructor, copy/move assignment, or destructor

– classes that have virtual members or virtual base classes

– classes that have volatile or reference members

– classes that have members in different (base) classes or with different access

– the types of lambda expressions (called closure types)

– functions

void

– types composed from these types

• Requires that the given type is either complete (see Section 10.3.1 on page 154) or (cv-qualified) void.

std:: is_literal_type < T >::value

• Yields true if the given type is a valid return type for a constexpr function (which notably excludes any type requiring nontrivial destruction).

• Yields true if T is a literal type:

– a scalar type (integral, float, enum, pointer; see is_scalar() on page 707)

– a reference

– a class type with at least one constexpr constructor that is not a copy/move constructor in each (base) class, no user-defined or virtual destructor in any (base) class or member, and where every initialization for nonstatic data members is a constant expression

– an array of such types

• Requires that the given type is either complete (see Section 10.3.1 on page 154) or (cv-qualified) void.

• Note that this trait is deprecated since C++17 because “it is too weak to be used meaningfully in generic code. What is really needed is the ability to know that a specific construction would produce constant initialization.

std:: is_empty < T >::value

• Yields true if T is a class type but not a union type, whose objects hold no data.

• Yields true if T is defined as class or struct with

– no nonstatic data members other than bit-fields of length 0

– no virtual member functions

– no virtual base classes

– no nonempty base classes

• Requires that the given type is complete (see Section 10.3.1 on page 154) if it is a class/struct (an incomplete union is fine).

std:: is_polymorphic < T >::value

• Yields true if T is polymorphic class type (a class that declares or inherits a virtual function).

• Requires that the given type is either complete (see Section 10.3.1 on page 154) or neither a class nor a struct.

std:: is_abstract < T >::value

• Yields true if T is an abstract class type (a class for which no objects can be created because it has at least one pure virtual member function).

• Requires that the given type is complete (see Section 10.3.1 on page 154) if it is a class/struct (an incomplete union is fine).

std:: is_final < T >::value

• Yields true if T is an final class type (a class or union that can’t serve as a base class because it is declared as being final).

• For all non-class/union types such as int, it returns false (thus, this is not the same as something like is derivable).

• Requires that the given type T is either complete (see Section 10.3.1 on page 154) or neither class/struct nor union.

• Available since C++14.

std:: has_virtual_destructor < T >::value

• Yields true if type T has a virtual destructor.

• Requires that the given type is complete (see Section 10.3.1 on page 154) if it is a class/struct (an incomplete union is fine).

std:: has_unique_object_representations < T >::value

• Yields true if any two objects of type T have the same object representation in memory. That is, two identical values are always represented using the same sequence of byte values.

• Objects with this property can produce a reliable hash value by hashing the associated byte sequence (there is no risk that some bits not participating in the object value might differ from one case to another).

• Requires that the given type is trivially copyable (see Section D.3.1 on page 712) and either complete (see Section 10.3.1 on page 154) or (cv-qualified) void or an array of unknown bounds.

• Available since C++17.

std:: alignment_of < T >::value

• Yields the alignment value of an object of type T as std::size_t (for arrays, the element type; for references, the referenced type).

• Same as: alignof(T)

• This trait was introduced in C++11 before the alignof(…) construct. It is still useful, however, because the trait can be passed around as a class type, which is useful for certain metaprograms.

• Requires that alignof(T) is a valid expression.

• Use aligned_union<> to get the common alignment of multiple types (see Section D.5 on page 733).

std:: rank < T >::value

• Yields the number of dimensions of an array of type T as std::size_t.

• Yields 0 for all other types.

• Pointers do not have any associated dimensions. An unspecified bound in an array type does specify a dimension. (As usual, a function parameter declared with an array type does not have an actual array type, and std::array is not an array type either. See Section D.2.1 on page 704.)

For example:

int a2[5][7];
rank_v<decltype(a2)>;        // yields 2
rank_v<int*>;                // yields 0 (no array)
extern int p1[];
rank_v<decltype(p1)>;        // yields1

std:: extent < T >::value

std:: extent < T, IDX >::value

• Yields the size of the first or IDX-th dimension of an array of type T as std::size_t.

• Yields 0, if T is not an array, the dimension doesn’t exist, or the size of the dimension is not known.

• See Section 19.8.2 on page 453 for implementation details.

int a2[5][7];
extent_v<decltype(a2)>;        // yields 5
extent_v<decltype(a2),0>;      // yields 5
extent_v<decltype(a2),1>;      // yields 7
extent_v<decltype(a2),2>;      // yields 0
extent_v<int*>;                // yields 0
extern int p1[];
extent_v<decltype(p1)>;        // yields 0

std:: underlying_type < T >::type

• Yields the underlying type of an enumeration type T.

• Requires that the given type is a complete (see Section 10.3.1 on page 154) enumeration type. For all other types, it has undefined behavior.

std:: is_invocable < T, Args… >::value

std:: is_nothrow_invocable < T, Args… >::value

• Yields true if T is usable as a callable for Args… (with the guarantee that no exception is thrown).

• That is, we can use these traits to test whether we can call or std::invoke() the given callable T for Args…. (See Section 11.1 on page 157 for details about callables and std::invoke().)

• Requires that all given types are complete (see Section 10.3.1 on page 154) or (cv-qualified) void or an array of unknown bounds.

• For example:

struct C {
  bool operator() (int) const {
    return true;
  }
};
std::is_invocable<C>::value         //false
std::is_invocable<C,int>::value     //true
std::is_invocable<int*>::value      //false
std::is_invocable<int(*)()>::value  //true

• Available since C++17.8

std:: is_invocable_r < RET_T, T, Args… >::value

std:: is_nothrow_invocable_r < RET_T, T, Args… >::value

• Yields true if we can use T as a callable for Args… (with the guarantee that no exception is thrown), returning a value convertible to type RET_T.

• That is, we can use these traits to test whether we can call or std::invoke() the passed callable T for Args… and use the return value as RET_T. (See Section 11.1 on page 157 for details about callables and std::invoke().)

• Requires that all passed types are complete (see Section 10.3.1 on page 154) or (cv-qualified) void or an array of unknown bounds.

• For example:

struct C {
  bool operator() (int) const {
   return true;
  }
};
std::is_invocable_r<bool,C,int>::value               //true
std::is_invocable_r<int,C,long>::value               //true
std::is_invocable_r<void,C,int>::value               //true
std::is_invocable_r<char*,C,int>::value              //false
std::is_invocable_r<long,int(*)(int)>::value         //false
std::is_invocable_r<long,int(*)(int),int>::value     //true
std::is_invocable_r<long,int(*)(int),double>::value  //true

• Available since C++17.

std:: invoke_result < T, Args… >::value

std:: result_of < T, Args… >::value

• Yields the return type of the callable T called for Args….

• Note that the syntax is slightly different:

– To invoke_result<> you have to pass both the type of the callable and the type of the arguments as parameters.

– To result_of<> you have to pass a “function declaration” using the corresponding types.

• If no call is possible, there is no type member defined, so that using it is an error (which might SFINAE out a function template using it in its declaration; see Section 8.4 on page 131).

• That is, we can use these traits to get the return type obtained when we call or std::invoke() the given callable T for Args…. (See Section 11.1 on page 157 for details about callables and std::invoke().)

• Requires that all given types are either complete (see Section 10.3.1 on page 154), (cv-qualified) void, or an array type of unknown bound.

invoke_result<> is available since C++17 and replaces result_of<>, which is deprecated since C++17, because invoke_result<> provides some improvements such the easier syntax and permitting abstract types for T.

• For example:

std::string foo(int);

using R0 = typename std::result_of<decltype(&foo)(int)>::type;  // C++11
using R1 = std::result_of_t<decltype(&foo)(int)>;        // C++14
using R2 = std::invoke_result_t<decltype(foo), int>;        // C++17

struct ABC {
   virtual ~ABC() = 0;
   void operator() (int) const {
   }
};

using T1 = typename std::result_of<ABC(int)>::type; // ERROR: ABC is abstract
using T2 = typename std::invoke_result<ABC, int>::type; // OK since C++17

See Section 11.1.3 on page 163 for a full example.

D.3.2 Test for Specific Operations

Trait Effect

is_constructible<T, Args…>

Can initialize type T with types Args

is_trivially_constructible<T, Args…>

Can trivially initialize type T with types Args

is_nothrow_constructible<T, Args…>

Can initialize type T with types Args and that operation can’t throw

is_default_constructible<T>

Can initialize T without arguments

is_trivially_default_constructible<T>

Can trivially initialize T without arguments

is_nothrow_default_constructible<T>

Can initialize T without arguments and that operation can’t throw

is_copy_constructible<T>

Can copy a T

is_trivially_copy_constructible<T>

Can trivially copy a T

is_nothrow_copy_constructible<T>

Can copy a T and that operation can’t throw

is_move_constructible<T>

Can move a T

is_trivially_move_constructible<T>

Can trivially move a T

is_nothrow_move_constructible<T>

Can move a T and that operation can’t throw

is_assignable<T, T2>

Can assign type T2 to type T

is_trivially_assignable<T, T2>

Can trivially assign type T2 to type T

is_nothrow_assignable<T, T2>

Can assign type T2 to type T and that operation can’t throw

is_copy_assignable<T>

Can copy assign a T

is_trivially_copy_assignable<T>

Can trivially copy assign a T

is_nothrow_copy_assignable<T>

Can copy assign a T and that operation can’t throw

is_move_assignable<T>

Can move assign a T

is_trivially_move_assignable<T>

Can trivially move assign a T

is_nothrow_move_assignable<T>

Can move assign a T and that operation can’t throw

is_destructible<T>

Can destroy a T

is_trivially_destructible<T>

Can trivially destroy a T

is_nothrow_destructible<T>

Can trivially destroy a T and that operation can’t throw

is_swappable<T>

Can call swap() for this type (since C++17)

is_nothrow_swappable<T>

Can call swap() for this type and that operation can’t throw (since C++17)

is_swappable_with<T, T2>

Can call swap() for these two types with specific value category (since C++17)

is_nothrow_swappable_with<T, T2>

Can call swap() for these two types with specific value category and that operation can’t throw (since C++17)

Table D.4. Traits to Check for Specific Operations

Table D.4 lists the type traits that allow us to check for some specific operations. The forms with is_trivially_… additionally check whether all (sub-)operations called for the object, members, or base classes are trivial (neither user-defined nor virtual). The forms with is_nothrow_… additionally check whether the called operation guarantees not to throw. Note that all is__constructible checks imply the corresponding is__destructible check. For example:

utils/isconstructible.cpp

#include <iostream>

class C {
  public:
    C() {  //default constructor has no noexcept
    }
    virtual ~C() = default; // makes C nontrivial
};

int main()
{
  using namespace std;
  cout << is_default_constructible_v<C> << ’\n’;            // true
  cout << is_trivially_default_constructible_v<C> << ’\n’;  // false
  cout << is_nothrow_default_constructible_v<C> << ’\n’;    // false
  cout << is_copy_constructible_v<C> << ’\n’;               // true
  cout << is_trivially_copy_constructible_v<C> << ’\n’;     // true
  cout << is_nothrow_copy_constructible_v<C> << ’\n’;       // true
  cout << is_destructible_v<C> << ’\n’;                     // true
  cout << is_trivially_destructible_v<C> << ’\n’;           // false
  cout << is_nothrow_destructible_v<C> << ’\n’;             // true
}

Due to the definition of a virtual constructor, all operations are no longer trivial. And because we define a default constructor without noexcept, it might throw. All other operations, by default, guarantee not to throw.

std:: is_constructible < T, Args… >::value

std:: is_trivially_constructible < T, Args… >::value

std:: is_nothrow_constructible < T, Args… >::value

• Yields true if an object of type T can be initialized with arguments of the types given by Args… (without using a nontrivial operation or with the guarantee that no exception is thrown). That is, the following must be valid:9

T t(std::declval<Args>()…);

• A true value implies that the object can be destroyed accordingly (i.e., is_destructible_v<T>, is_trivially_destructible_v<T>, or is_nothrow_destructible_v<T> yields true).

• Requires that all given types are either complete (see Section 10.3.1 on page 154), (cv-qualified) void, or arrays of unknown bound.

• For example:

is_constructible_v<int>                              // true
is_constructible_v<int,int>                          // true
is_constructible_v<long,int>                         // true
is_constructible_v<int,void*>                        // false
is_constructible_v<void*,int>                        // false
is_constructible_v<char const*,std::string>          // false
is_constructible_v<std::string,char const*>          // true
is_constructible_v<std::string,char const*,int,int>  // true

• Note that is_convertible has a different order for the source and destination types.

std:: is_default_constructible < T >::value

std:: is_trivially_default_constructible < T >::value

std:: is_nothrow_default_constructible < T >::value

• Yields true if an object of type T can be initialized without any argument for initialization (without using a nontrivial operation or with the guarantee that no exception is thrown).

• Same as is_constructible_v<T>, is_trivially_constructible_v<T>, or is_nothrow_constructible_v<T>, respectively.

• A true value implies that the object can be destroyed accordingly (i.e., is_destructible_v<T>, is_trivially_destructible_v<T>, or is_nothrow_destructible_v<T> yields true).

• Requires that the given type is either complete (see Section 10.3.1 on page 154), (cv-qualified) void, or an array of unknown bound.

std:: is_copy_constructible < T >::value

std:: is_trivially_copy_constructible < T >::value

std:: is_nothrow_copy_constructible < T >::value

• Yields true if an object of type T can be created by copying another value of type T (without using a nontrivial operation or with the guarantee that no exception is thrown).

• Yields false if T is not a referenceable type (either (cv-qualified) void or a function type that is qualified with const, volatile, &, and/or &&).

• Provided T is a referenceable type, same as is_constructible<T,T const&>::value, is_trivially_constructible<T,T const&>::value, or is_nothrow_constructible<T,T const&>::value respectively.

• To find out whether an object of T would be copy constructible from an rvalue of type T, use is_constructible<T,T&&>, and so on.

• A true value implies that the object can be destroyed accordingly (i.e., is_destructible_v<T>, is_trivially_destructible_v<T>, or is_nothrow_destructible_v<T> yields true).

• Requires that the given type is either complete (see Section 10.3.1 on page 154), (cv-qualified) void, or an array of unknown bound.

• For example:

is_copy_constructible_v<int>                    // yields true
is_copy_constructible_v<void>                   // yields false
is_copy_constructible_v<std::unique_ptr<int>>   // yields false
is_copy_constructible_v<std::string>            // yields true
is_copy_constructible_v<std::string&>           // yields true
is_copy_constructible_v<std::string&&>          // yields false
// in contrast to:
is_constructible_v<std::string,std::string>     // yields true
is_constructible_v<std::string&,std::string&>   // yields true
is_constructible_v<std::string&&,std::string&&> // yields true

std:: is_move_constructible < T >::value

std:: is_trivially_move_constructible < T >::value

std:: is_nothrow_move_constructible < T >::value

• Yields true if an object of type T can be created from an rvalue of type T (without using a nontrivial operation or with the guarantee that no exception is thrown).

• Yields false if T is not a referenceable type (either (cv-qualified) void or a function type that is qualified with const, volatile, &, and/or &&).

• Provided T is a referenceable type, same as is_constructible<T,T&&>::value, is_trivially_constructible<T,T&&>::value, or is_nothrow_constructible<T,T&&>::value respectively.

• A true value implies that the object can be destroyed accordingly (i.e., is_destructible_v<T>, is_trivially_destructible_v<T>, or is_nothrow_destructible_v<T> yields true).

• Note that there is no way to check whether a move constructor throws without being able to call it directly for an object of type T. It is not enough for the constructor to be public and not deleted; it also requires that the corresponding type is not an abstract class (references or pointers to abstract classes work fine).

• See Section 19.7.2 on page 443 for implementation details.

• For example:

is_move_constructible_v<int>                    // yields true
is_move_constructible_v<void>                   // yields false
is_move_constructible_v<std::unique_ptr<int>>   // yields true
is_move_constructible_v<std::string>            // yields true
is_move_constructible_v<std::string&>           // yields true
is_move_constructible_v<std::string&&>          // yields true
// in contrast to:
is_constructible_v<std::string,std::string>     // yields true
is_constructible_v<std::string&,std::string&>   // yields true
is_constructible_v<std::string&&,std::string&&> // yields true

std:: is_assignable < TO, FROM >::value

std:: is_trivially_assignable < TO, FROM >::value

std:: is_nothrow_assignable < TO, FROM >::value

• Yields true if an object of type FROM can be assigned to an object of type TO (without using a nontrivial operation or with the guarantee that no exception is thrown).

• Requires that the given types are either complete (see Section 10.3.1 on page 154), (cv-qualified) void, or arrays of unknown bound.

• Note that is_assignable_v<> for a nonreference, nonclass type as first type always yields false, because such types produce prvalues. That is, the statement 42 = 77; is not valid. For class types, however, rvalues may be assigned to, given an appropriate assignment operator (due to an old rule that non-const member functions can be invoked on rvalues of class types).10

• Note that is_convertible has a different order for the source and destination types.

• For example:

is_assignable_v<int,int>                     // yields false
is_assignable_v<int&,int>                    // yields true
is_assignable_v<int&&,int>                   // yields false
is_assignable_v<int&,int&>                   // yields true
is_assignable_v<int&&,int&&>                 // yields false
is_assignable_v<int&,long&>                  // yields true
is_assignable_v<int&,void*>                  // yields false
is_assignable_v<void*,int>                   // yields false
is_assignable_v<void*,int&>                  // yields false
is_assignable_v<std::string,std::string>     // yields true
is_assignable_v<std::string&,std::string&>   // yields true
is_assignable_v<std::string&&,std::string&&> // yields true

std:: is_copy_assignable < T >::value

std:: is_trivially_copy_assignable < T >::value

std:: is_nothrow_copy_assignable < T >::value

• Yields true if a value of type T can be (copy-)assigned to an object of type T (without using a nontrivial operation or with the guarantee that no exception is thrown).

• Yields false if T is not a referenceable type (either (cv-qualified) void or a function type that is qualified with const, volatile, &, and/or &&).

• Provided T is a referenceable type, same as is_assignable<T&,T const&>::value, is_trivially_assignable<T&,T const&>::value, or is_nothrow_assignable<T&,T const&>::value respectively.

• To find out whether an rvalue of type T can be copy assigned to another rvalue of type T, use is_assignable<T&&,T&&>, and so on.

• Note that void, built-in array types, and classes with deleted copy-assignment operator cannot be copy-assigned.

• Requires that the given type is either complete (see Section 10.3.1 on page 154), (cv-qualified) void, or an array of unknown bound.

• For example:

is_copy_assignable_v<int>                    // yields true
is_copy_assignable_v<int&>                   // yields true
is_copy_assignable_v<int&&>                  // yields true
is_copy_assignable_v<void>                   // yields false
is_copy_assignable_v<void*>                  // yields true
is_copy_assignable_v<char[]>                 // yields false
is_copy_assignable_v<std::string>            // yields true
is_copy_assignable_v<std::unique_ptr<int>>   // yields false

std:: is_move_assignable < T >::value

std:: is_trivially_move_assignable < T >::value

std:: is_nothrow_move_assignable < T >::value

• Yields true if an rvalue of type T can be move-assigned to an object of type T (without using a nontrivial operation or with the guarantee that no exception is thrown).

• Yields false if T is not a referenceable type (either (cv-qualified) void or a function type that is qualified with const, volatile, &, and/or &&).

• Provided T is a referenceable type, same as is_assignable<T&,T&&>::value, is_trivially_assignable<T&,T&&>::value, or is_nothrow_assignable<T&,T&&>::value respectively.

• Note that void, built-in array types, and classes with deleted move-assignment operator cannot be move-assigned.

• Requires that the given type is either complete (see Section 10.3.1 on page 154) or (cv-qualified) void or an array of unknown bound.

• For example:

is_move_assignable_v<int>                    // yields true
is_move_assignable_v<int&>                   // yields true
is_move_assignable_v<int&&>                  // yields true
is_move_assignable_v<void>                   // yields false
is_move_assignable_v<void*>                  // yields true
is_move_assignable_v<char[]>                 // yields false
is_move_assignable_v<std::string>            // yields true
is_move_assignable_v<std::unique_ptr<int>>   // yields true

std:: is_destructible < T >::value

std:: is_trivially_destructible < T >::value

std:: is_nothrow_destructible < T >::value

• Yields true if an object of type T can be destroyed (without using a nontrivial operation or with the guarantee that no exception is thrown).

• Always yields true for references.

• Always yields false for void, array types with unknown bounds, and function types.

is_trivially_destructible yields true if no destructor of T, any base class, or any nonstatic data member is user-defined or virtual.

• Requires that the given type is either complete (see Section 10.3.1 on page 154), (cv-qualified) void, or an array of unknown bound.

• For example:

is_destructible_v<void>                                // yields false
is_destructible_v<int>                                   // yields true
is_destructible_v<std::string>                           // yields true
is_destructible_v<std::pair<int,std::string>>            // yields true

is_trivially_destructible_v<void>                        // yields false
is_trivially_destructible_v<int>                         // yields true
is_trivially_destructible_v<std::string>                 // yields false
is_trivially_destructible_v<std::pair<int,int>>          // yields true
is_trivially_destructible_v<std::pair<int,std::string>>  // yields false

std:: is_swappable_with < T1, T2 >::value

std:: is_nothrow_swappable_with < T1, T2 >::value

• Yields true if an expression of type T1 can be swap()’ed with an expression of type T2 except that reference types only determine the value category of the expression (with the guarantee that no exception is thrown).

• Requires that the given types are either complete (see Section 10.3.1 on page 154), (cv-qualified) void, or arrays of unknown bound.

• Note that is_swappable_with_v<> for a nonreference, nonclass type as first or second type always yields false, because such types produce prvalues. That is, swap(42,77) is not valid.

• For example:

is_swappable_with_v<int,int>                     // yields false
is_swappable_with_v<int&,int>                    // yields false
is_swappable_with_v<int&&,int>                   // yields false
is_swappable_with_v<int&,int&>                   // yields true
is_swappable_with_v<int&&,int&&>                 // yields false
is_swappable_with_v<int&,long&>                  // yields false
is_swappable_with_v<int&,void*>                  // yields false
is_swappable_with_v<void*,int>                   // yields false
is_swappable_with_v<void*,int&>                  // yields false
is_swappable_with_v<std::string,std::string>     // yields false
is_swappable_with_v<std::string&,std::string&>   // yields true
is_swappable_with_v<std::string&&,std::string&&> // yields false

• Available since C++17.

std:: is_swappable < T >::value

std:: is_nothrow_swappable < T >::value

• Yields true if lvalues of type T can be swapped (with the guarantee that no exception is thrown).

• Provided T is a referenceable type. same as is_swappable_with<T&,T&>::value or is_nothrow_swappable_with<T&,T&>::value respectively.

• Yields false if T is not a referenceable type (either (cv-qualified) void or a function type that is qualified with const, volatile, &, and/or &&).

• To find out whether an rvalue of T would be swappable with another rvalue of T, use is_swappable_with<T&&,T&&>.

• Requires that the given type is a complete type (Section 10.3.1 on page 154), (cv-qualified) void, or an array of unknown bound.

• For example:

is_swappable_v<int>                    // yields true
is_swappable_v<int&>                   // yields true
is_swappable_v<int&&>                  // yields true
is_swappable_v<std::string&&>          // yields true
is_swappable_v<void>                   // yields false
is_swappable_v<void*>                  // yields true
is_swappable_v<char[]>                 // yields false
is_swappable_v<std::unique_ptr<int>>   // yields true

• Available since C++17.

D.3.3 Relationships Between Types

Table D.5 lists the type traits that allow testing certain relationships between types. This includes checking which constructors and assignment operators are provided for class types.

Trait Effect

is_same<T1, T2>

T1 and T2 are the same types (including const/volatile qualifiers)

is_base_of<T, D>

Type T is base class of type D

is_convertible<T, T2>

Type T is convertible into type T2

Table D.5. Traits to Test Type Relationships

std:: is_same < T1, T2 >::value

• Yields true if T1 and T2 name the same type including cv-qualifiers (const and volatile).

• Yields true if a type is a type alias of another.

• Yields true if two objects were initialized by objects of the same type.

• Yields false for the (closure) types associated with two distinct lambda expressions even if they define the same behavior.

• For example:

auto a = nullptr;
auto b = nullptr;
is_same_v<decltype(a),decltype(b)>     // yields true

using A = int;
is_same_v<A,int>                       // yields true

auto x = [] (int) {};
auto y = x;
auto z = [] (int) {};
is_same_v<decltype(x),decltype(y)>     // yields true
is_same_v<decltype(x),decltype(z)>     // yields false

• See Section 19.3.3 on page 410 for implementation details.

std:: is_base_of < B, D >::value

• Yields true if B is a base class of D or B is the same class as D.

• It doesn’t matter whether a type is cv-qualified, private or protected inheritance is used, D has multiple base classes of type B, or D has B as a base class via multiple inheritance paths (via virtual inheritance).

• Yields false if at least one of the types is a union.

• Requires that type D is either complete (see Section 10.3.1 on page 154), has the same type as B (ignoring any const/volatile qualification), or is neither a struct nor a class.

• For example:

class B {
};
class D1 : B {
}; class D2 : B {
}; class DD : private D1, private D2 {
};
is_base_of_v<B, D1>        // yields true
is_base_of_v<B, DD>        // yields true
is_base_of_v<B const, DD>  // yields true
is_base_of_v<B, DD const>  // yields true
is_base_of_v<B, B const>   // yields true
is_base_of_v<B&, DD&>      // yields false (no class type)
is_base_of_v<B[3], DD[3]>  // yields false (no class type)
is_base_of_v<int, int>     // yields false (no class type)

std:: is_convertible < FROM, TO >::value

• Yields true if expression of type FROM is convertible to type TO. Thus, the following must be valid:11

TO test() {
  return std::declval<FROM>();
}

• A reference on top of type FROM is only used to determine the value category of the expression being converted; the underlying type is then the type of the source expression.

• Note that is_constructible does not always imply is_convertible. For example:

class C {
  public:
    explicit C(C const&);  // no implicit copy constructor
    …
};
is_constructible_v<C,C>   // yields true
is_convertible_v<C,C>     // yields false

• Requires that the given types are either complete (see Section 10.3.1 on page 154), (cv-qualified) void, or arrays of unknown bound.

• Note that is_constructible (see Section D.3.2 on page 719) and is_assignable (see Section D.3.2 on page 721) have a different order for the source and destination types.

• See Section 19.5 on page 428 for implementation details.

D.4 Type Construction

The traits listed in Table D.6 allow us to construct types from other types.

Trait Effect

remove_const<T>

Corresponding type without const

remove_volatile<T>

Corresponding type without volatile

remove_cv<T>

Corresponding type without const and volatile

add_const<T>

Corresponding const type

add_volatile<T>

Corresponding volatile type

add_cv<T>

Corresponding const volatile type

make_signed<T>

Corresponding signed nonreference type

make_unsigned<T>

Corresponding unsigned nonreference type

remove_reference<T>

Corresponding nonreference type

add_lvalue_reference<T>

Corresponding lvalue reference type (rvalues become lvalues)

add_rvalue_reference<T>

Corresponding rvalue reference type (lvalues remain lvalues)

remove_pointer<T>

Referred type for pointers (same type otherwise)

add_pointer<T>

Type of pointer to corresponding nonreference type

remove_extent<T>

Element types for arrays (same type otherwise)

remove_all_extents<T>

Element type for multidimensional arrays (same type otherwise)

decay<T>

Transfers to corresponding “by-value” type

Table D.6. Traits for Type Construction

std:: remove_const < T >::type

std:: remove_volatile < T >::type

std:: remove_cv < T >::type

• Yields the type T without const or/and volatile at the top level.

• Note that a const pointer is a const-qualified type, whereas a non-const pointer or reference to a const type is not const-qualified. For example:

remove_cv_t<int>                  // yields int
remove_const_t<int const>         // yields int
remove_cv_t<int const volatile>   // yields int
remove_const_t<int const&>        // yields int const& (only refers to int const)

Clearly, the order in which type construction traits are applied matters:12

remove_const_t<remove_reference_t<int const&>>        // yields int
remove_reference_t<remove_const_t<int const&>>
        // yields int const

Instead, we may prefer to use std::decay<>, which, however, also converts array and function types to corresponding pointer types (see Section D.4 on page 731):

decay_t<int const&>        // yields int

• See Section 19.3.2 on page 406 for implementation details.

std:: add_const < T >::type

std:: add_volatile < T >::type

std:: add_cv < T >::type

• Yields the type of T with const or/and volatile qualifiers added at the top level.

• Applying one of these traits to a reference type or a function type has no effect. For example:

add_cv_t<int>                       // yields int const volatile
add_cv_t<int const>                 // yields int const volatile
add_cv_t<int const volatile>        // yields int const volatile
add_const_t<int>                    // yields int const
add_const_t<int const>              // yields int const
add_const_t<int&>                   // yields int&

std:: make_signed < T >::type

std:: make_unsigned < T >::type

• Yields the corresponding signed/unsigned type of T.

• Requires that T is an enumeration type or a (cv-qualified) integral type other than bool. All other types lead to undefined behavior (see Section 19.7.1 on page 442 for a discussion about how to avoid this undefined behavior).

• Applying one of these traits to a reference type or a function type has no effect, whereas a non-const pointer or reference to a const type is not const-qualified. For example:

make_unsigned_t<char>              // yields unsigned char
make_unsigned_t<int>               // yields unsigned int
make_unsigned_t<int const&>        // undefined behavior

std:: remove_reference < T >::type

• Yields the type the reference type T refers to (or T itself if it is not a reference type).

• For example:

remove_reference_t<int>               // yields int
remove_reference_t<int const>         // yields int const
remove_reference_t<int const&>        // yields int const
remove_reference_t<int&&>             // yields int

• Note that a reference type itself is not a const type. For this reason, the order of applying type construction traits matters:13

remove_const_t<remove_reference_t<int const&>>        // yields int
remove_reference_t<remove_const_t<int const&>>        // yields int const

Instead, we may prefer to use std::decay<>, which, however, also converts array and function types to corresponding pointer types (see Section D.4 on page 731):

decay_t<int const&>        // yields int

• See Section 19.3.2 on page 404 for implementation details.

std:: add_lvalue_reference < T >::type

std:: add_rvalue_reference < T >::type

• Yields an lvalue or rvalue reference to T if T is a referenceable type.

• Yields T if T is not referenceable (either (cv-qualified) void or a function type that is qualified with const, volatile, &, and/or &&).

• Note that if T already is a reference type, the traits use the reference collapsing rules (see Section 15.6.1 on page 277): The result is an rvalue reference only if add_rvalue_reference is used and T is an rvalue reference.

• For example:

add_lvalue_reference_t<int>        // yields int&
add_rvalue_reference_t<int>        // yields int&&
add_rvalue_reference_t<int const>  // yields int const&&
add_lvalue_reference_t<int const&> // yields int const&
add_rvalue_reference_t<int const&> // yields int const& (reference collapsing rules)
add_rvalue_reference_t<remove_reference_t<int const&>>   // yields int&&
add_lvalue_reference_t<void>       // yields void
add_rvalue_reference_t<void>       // yields void

• See Section 19.3.2 on page 405 for implementation details.

std:: remove_pointer < T >::type

• Yields the type the pointer type T points to (or T itself if it is not a pointer type).

• For example:

remove_pointer_t<int>                      // yields int
remove_pointer_t<int const*>               // yields int const
remove_pointer_t<int const* const* const>  // yields int const* const

std:: add_pointer < T >::type

• Yields the type of a pointer to T, or, in the case of a reference type T, the type of a pointer to underlying type of T.

• Yields T if there is no such type (applies to cv-qualified function types).

• For example:

add_pointer_t<void>              // yields void*
add_pointer_t<int const* const>  // yields int const* const*
add_pointer_t<int&>              // yields int*
add_pointer_t<int[3]>            // yields int(*)[3]
add_pointer_t<void(&)(int)>      // yields void(*)(int)
add_pointer_t<void(int)>         // yields void(*)(int)
add_pointer_t<void(int) const>   // yields void(int) const (no change)

std:: remove_extent < T >::type

std:: remove_all_extents < T >::type

• Given an array type, remove_extent produces its immediate element type (which could itself be an array type) and remove_all_extents strips all “array layers” to produce the underlying element type (which is therefore no longer an array type). If T is not an array type, T itself is produced.

• Pointers do not have any associated dimensions. An unspecified bound in an array type does specify a dimension. (As usual, a function parameter declared with an array type does not have an actual array type, and std::array is not an array type either. See Section D.2.1 on page 704.)

• For example:

remove_extent_t<int>              // yields int
remove_extent_t<int[10]>          // yields int
remove_extent_t<int[5][10]>       // yields int[10]
remove_extent_t<int[][10]>        // yields int[10]
remove_extent_t<int*>             // yields int*
remove_all_extents_t<int>         // yields int
remove_all_extents_t<int[10]>     // yields int
remove_all_extents_t<int[5][10]>  // yields int
remove_all_extents_t<int[][10]>   // yields int
remove_all_extents_t<int(*)[5]>   // yields int(*)[5]

• See Section 23.1.2 on page 531 for implementation details.

std:: decay < T >::type

• Yields the decayed type of T.

• In detail, for type T the following transformations are performed:

– First, remove_reference (see Section D.4 on page 729) is applied.

– If the result is an array type, a pointer to the immediate element type is produced (see Section 7.1 on page 107).

– Otherwise, if the result is a function type, the type yielded by add_pointer for that function type is produced (see Section 11.1.1 on page 159).

– Otherwise, that result is produced without any top-level const/volatile qualifiers.

decay<> models by-value passing of arguments or the type conversions when initializing an objects of type auto.

decay<> is particularly useful to handle template parameters that may be substituted by reference types but used to determine a return type or a parameter type of another function. See Section 1.3.2 on page 12 and Section 7.6 on page 120 for examples discussing and using std::decay<>() (the latter with the history of implementing std::make_pair<>()).

• For example:

decay_t<int const&>        // yields int
decay_t<int const[4]>      // yields int const*
void foo();
decay_t<decltype(foo)>     // yields void(*)()

• See Section 19.3.2 on page 407 for implementation details.

D.5 Other Traits

Table D.7 lists all remaining type traits. They query special properties or provide more complicated type transformations.

Trait Effect

enable_if<B, T=void >

Yields type T only if bool B is true

conditional<B, T, F>

Yields type T if bool B is true and type F otherwise

common_type<T1,… >

Common type of all passed types

aligned_storage<Len>

Type of Len bytes with default alignment

aligned_storage<Len, Align>

Type of Len bytes aligned according to a divisor of size_t Align

aligned_union<Len, Types…>

Type of Len bytes aligned for a union of Types…

Table D.7. Other Type Traits

std:: enable_if < cond >::type

std:: enable_if < cond, T >::type

• Yields void or T in its member type if cond is true. Otherwise, it does not define a member type.

• Because the type member is not defined when the cond is false, this trait can and is usually used to disable or SFINAE out a function template based on the given condition.

• See Section 6.3 on page 98 for details and a first example. See Section D.6 on page 735 for another example using parameter packs.

• See Section 20.3 on page 469 for details about how std::enable_if is implemented.

std:: conditional < cond, T, F >::type

• Yields T if cond is true, and F otherwise.

• This is the standard version of the trait IfThenElseT introduced in Section 19.7.1 on page 440.

• Note that, unlike a normal C++ if-then-else statement, the template arguments for both the “then” and “else” branches are evaluated before the selection is made, so neither branch may contain ill-formed code or the program is likely to be ill-formed. As a consequence, you might have to add a level of indirection to avoid that expressions in the “then” and “else” branches are evaluated if the branch is not used. Section 19.7.1 on page 440 demonstrates this for the trait IfThenElseT, which has the same behavior.

• See Section 11.5 on page 171 for an example.

• See Section 19.7.1 on page 440 for details about how std::conditional is implemented.

std:: common_type < T… >::type

• Yields the “common type” of the given types T1, T2, …, Tn.

• The computation of a common type is a little more complex than we want to cover in this appendix. Roughly speaking, the common type of two types U and V is the type produced by the conditional operator ?: when its second and third operands are of those types U and V (with reference types used only to determine the value category of the two operands); there is no common type if that is invalid. decay_t (see page 731) is applied to this result. This default computation may be overridden by user specialization of std::common_type<U, V> (in the C++ standard library, partial specialization exists for durations and time points).

• If no type is given or no common type exists, there is no type member defined, so that using it is an error (which might SFINAE out a function template using it).

• If a single type is given, the result is the application of decay_t to that type.

• For more than two types, common_type recursively replaces the first two types T1 and T2 by their common type. If at any time that process fails, there is no common type.

• While processing the common type, the passed types are decays, so that the trait always yields a decayed type (see Section D.4 on page 731).

• See Section 1.3.3 on page 12 for a discussion and example of the application of this trait.

• The core of the primary template of this trait is usually implemented by something like the following (here using only two parameters):

template<typename T1, typename T2>
struct common_type<T1,T2> {
using type = std::decay_t<decltype(true ? std::declval<T1>()
                                        : std::declval<T2>())>;
};

std:: aligned_union < MIN_SZ, T… >::type

• Yields a plain old datatype (POD) usable as uninitialized storage that has a size of at least MIN_SZ and is suitable to hold any of the given types T1, T2, …, Tn.

• In addition, it yields a static member alignment_value whose value is the strictest alignment of all the given types, which for the result type is equivalent to

std::alignment_of_v<type>::value (see Section D.3.1 on page 715)

alignof(type)

• Requires that at least one type is provided.

• For example:

using POD_T = std::aligned_union_t<0, char,
                                   std::pair<std::string,std::string>>;
std::cout << sizeof(POD_T) << ’\n’;
std::cout << std::aligned_union<0, char,
                                   std::pair<std::string,std::string>
                                >::alignment_value;
        << ’\n’;

Note the use of aligned_union instead of aligned_union_t to get the value for the alignment instead of the type.

std:: aligned_storage < MAX_TYPE_SZ >::type

std:: aligned_storage < MAX_TYPE_SZ, DEF_ALIGN >::type

• Yields a plain old data type (POD) usable as uninitialized storage that has a size to hold all possible types with a size up to MAX_TYPE_SZ, taking the default alignment or the alignment passed as DEF_ALIGN into account.

• Requires that MAX_TYPE_SZ is greater than zero and the platform has at least one type with the alignment value DEF_ALIGN.

• For example:

using POD_T = std::aligned_storage_t<5>;

D.6 Combining Type Traits

In most contexts, multiple type trait predicates can be combined by using logical operators. However, in some contexts of template metaprogramming, this is not enough:

• If you have to deal with traits that might fail (e.g., due to incomplete types).

• If you want to combine type trait definitions.

The type traits std::conjunction<>, std::disjunction<>, and std::negation<> are provided for this purpose.

One example is that these helpers short-circuit Boolean evaluations (abort the evaluation after the first false for && or first true for ||, respectively).14 For example, if incomplete types are used:

struct X {
   X(int);         // converts from int
};
struct Y;
        // incomplete type

the following code might not compile because is_constructible results in undefined behavior for incomplete types (some compilers accept this code, though):

// undefined behavior:
static_assert(std::is_constructible<X,int>{}>
              || std::is_constructible<Y,int>{},
            "can’t init X or Y from int");

Instead, the following is guaranteed to compile, since the evaluation of is_constructible<X,int> already yields true:

// OK:
static_assert(std::disjunction<std::is_constructible<X, int>,
                                  std::is_constructible<Y, int>>{},
             "can’t init X or Y from int");

The other application is an easy way to define new type traits by logically combining existing type traits. For example, you can easily define a trait that checks whether a type is “not a pointer” (neither a pointer, nor a member pointer, nor a null pointer):

template<typename T>
struct isNoPtrT : std::negation<std::disjunction<std::is_null_pointer<T>,
                               std::is_member_pointer<T>,
                               std::is_pointer<T>>>
{
};

Here we can’t use the logical operators, because we combine the corresponding trait classes. With this definition, the following is possible:

std::cout << isNoPtrT<void*>::value << ’\n’;               //false
std::cout << isNoPtrT<std::string>::value << ’\n’;         //true
auto np = nullptr;
std::cout << isNoPtrT<decltype(np)>::value << ’\n’;       //false

And with a corresponding variable template:

template<typename T>
constexpr bool isNoPtr = isNoPtrT<T>::value;

we can write:

std::cout << isNoPtr<void*> << ’\n’;        //false
std::cout << isNoPtr<int> << ’\n’;          //true

As a last example, the following function template is only enabled if all its template arguments are neither classes not unions:

template<typename… Ts>
std::enable_if_t<std::conjunction_v<std::negation<std::is_class<Ts>>…,
                                    std::negation<std::is_union<Ts>>…
                                  >>
print(Ts…)
{
   …
}

Note that the ellipsis is placed behind each negation so that it applies to each element of the parameter pack.

Trait Effect conjunction<B…> Logical and for Boolean traits B… (since C++17) disjunction<B…> Logical or for Boolean traits B… (since C++17) negation<B> Logical not for Boolean trait B (since C++17)

Trait

Effect

conjunction<B... >

Logical and for Boolean traits B... (since C++17)

disjunction<B... >

Logical or for Boolean traits B... (since C++17)

negation<B>

Logical not for Boolean trait B (since C++17)

Table D.8. Type Traits to Combine Other Type Traits

std:: conjunction < B… >::value

std:: disjunction < B… >::value

• Yields whether all or one of the passed Boolean traits B… yield(s) true.

• Logically applies operator && or ||, respectively, to the passed traits.

• Both traits short-circuit (abort the evaluation after the first false or true). See the motivating example above.

• Available since C++17.

std:: negation < B >::value

• Yields whether the passed Boolean trait B yields false.

• Logically applies operator ! to the passed trait.

• See the motivating example above.

• Available since C++17.

D.7 Other Utilities

The C++ standard library provides a few other utilities that are broadly useful to write portable generic code.

Trait

Effect

declval<T >()

yields an “object” (rvalue reference) of a type without constructing it

addressof(r)

yields the address of an object or function

Table D.9. Other Utilities for Metaprogramming

std:: declval <T> ()

• Defined in header <utility>.

• Yields an “object” or function of any type without calling any constructor or initialization.

• If T is void, the return type is void.

• This can be used to deal with objects or functions of any type in unevaluated expressions.

• It is simply defined as follows:

template<typename T>
add_rvalue_reference_t<T> declval() noexcept;

Thus:

– If T is a plain type or an rvalue reference, it yields a T&&.

– If T is an lvalue reference, it yields a T&.

– If T is void, it yields void.

• See Section 19.3.4 on page 415 for details and Section 11.2.3 on page 166 and the common_type<> type trait in Section D.5 on page 732 for examples using it.

std:: addressof (r)

• Defined in header <memory>.

• Yields the address of object or function r even if operator& is overloaded for its type.

• See Section 11.2.2 on page 166 for details.

1 Before C++17, the standard did not include the alias template bool_constant<>. std::true_type and std::false_type did exist in C++11 and C++14, however, and were specified directly in terms of integral_constant<bool,true> and integral_constant<bool,false>, respectively.

2 The C++ standardization committee considered a proposal for C++17 to require that violations of preconditions of type traits always result in a compile-time error. However, because some type traits currently have requirements that are stronger than strictly necessary (such as always requiring complete types) this change was postponed.

3 Thanks to Howard Hinnant for providing this type hierarchy in http://howardhinnant.github.io/TypeHiearchy.pdf

4 Before C++14, the only exception was the type of nullptr, std::nullptr_t, for which all primary type category utilities yielded false, because is_null_pointer<> was not part of C++11.

5 This was clarified by the resolution of core issue 1059 after C++11 was published.

6 This was clarified by the resolution of core issue 1059 after C++11 was published.

7 Note that the base classes and/or data members of aggregates don’t have to be aggregates. Prior to C++14, aggregate class types could not have default member initializers. Prior to C++17, aggregates could not have public base classes.

8 Late in the standardization process of C++17, is_invocable was renamed from is_callable.

9 See Section 11.2.3 on page 166 for the effect of std::declval.

10 Thanks to Daniel Krügler for pointing this out.

11 See Section 11.2.3 on page 166 for the effect of std::declval.

12 The next standard after C++17 will probably provide a remove_refcv trait for this reason.

13 The next standard after C++17 will probably provide a remove_refcv trait for this reason.

14 Thanks to Howard Hinnant for pointing this out.