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).
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;
//true
std::cout << std::is_same<decltype(c), int const>::value
<<’\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::coutsame \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.
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;
}
};
}
• 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.
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.
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.
Figure D.1. Primary and Composite Type Categories
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 |
Type |
|
|
Integral type (including |
|
Floating-point type ( |
|
Ordinary array type (not type |
|
Pointer type (including function pointer but not pointer to non-static member) |
|
Type of |
|
Pointer to a nonstatic data member |
|
Pointer to a nonstatic member function |
|
Lvalue reference |
|
Rvalue reference |
|
Enumeration type |
|
Class/struct or lambda type but not a union type |
|
Union type |
|
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
.
• 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.
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.
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 |
|
Lvalue or rvalue reference |
|
Pointer to nonstatic member |
|
Integral (including |
|
|
|
Integral (including |
|
Any type except |
|
The opposite of |
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>
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.
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 |
Signed arithmetic type |
|
|
Unsigned arithmetic type |
|
|
|
|
|
Aggregate type (since C++17) |
|
Scalar, trivial class, or arrays of these types |
|
Scalar, trivially copyable class, or arrays of these types |
|
Scalar, standard layout class, or arrays of these types |
|
Plain old data type (type where |
|
Scalar, reference, class, or arrays of these types (deprecated since C++17) |
|
Class with no members, virtual member functions, or virtual base classes |
|
Class with a (derived) virtual member function |
|
Abstract class (at least one pure virtual function) |
|
Final class (a class not allowed to derive from, since C++14) |
|
Class with virtual destructor |
|
Any two object with same value have same representation in memory (since C++17) |
|
Equivalent to |
|
Number of dimensions of an array type (or |
|
Extent of dimension I (or |
|
Underlying type of an enumeration type |
|
Can be used as callable for Args… (since C++17) |
|
Can be used as callable for Args… returning RT (since C++17) |
|
Result type if used as callable for Args… (since C++17) |
|
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.
• 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.”
• 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.
Trait | Effect |
|
Can initialize type T with types Args |
|
Can trivially initialize type T with types Args |
|
Can initialize type T with types Args and that operation can’t throw |
|
Can initialize T without arguments |
|
Can trivially initialize T without arguments |
|
Can initialize T without arguments and that operation can’t throw |
|
Can copy a T |
|
Can trivially copy a T |
|
Can copy a T and that operation can’t throw |
|
Can move a T |
|
Can trivially move a T |
|
Can move a T and that operation can’t throw |
|
Can assign type T2 to type T |
|
Can trivially assign type T2 to type T |
|
Can assign type T2 to type T and that operation can’t throw |
|
Can copy assign a T |
|
Can trivially copy assign a T |
|
Can copy assign a T and that operation can’t throw |
|
Can move assign a T |
|
Can trivially move assign a T |
|
Can move assign a T and that operation can’t throw |
|
Can destroy a T |
|
Can trivially destroy a T |
|
Can trivially destroy a T and that operation can’t throw |
|
Can call |
|
Can call |
|
Can call |
|
Can call |
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:
#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.
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.
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.
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 |
|
T1 and T2 are the same types (including |
|
Type T is base class of type D |
|
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.
The traits listed in Table D.6 allow us to construct types from other types.
Trait | Effect |
|
Corresponding type without |
|
Corresponding type without |
|
Corresponding type without |
|
Corresponding |
|
Corresponding |
|
Corresponding |
|
Corresponding signed nonreference type |
|
Corresponding unsigned nonreference type |
|
Corresponding nonreference type |
|
Corresponding lvalue reference type (rvalues become lvalues) |
|
Corresponding rvalue reference type (lvalues remain lvalues) |
|
Referred type for pointers (same type otherwise) |
|
Type of pointer to corresponding nonreference type |
|
Element types for arrays (same type otherwise) |
|
Element type for multidimensional arrays (same type otherwise) |
|
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
// yields
remove_reference_t<remove_const_t<int const&>>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_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.
Table D.7 lists all remaining type traits. They query special properties or provide more complicated type transformations.
Trait | Effect |
|
Yields type T only if |
|
Yields type T if |
|
Common type of all passed types |
|
Type of Len bytes with default alignment |
|
Type of Len bytes aligned according to a divisor of |
|
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 T
1, T
2, …, T
n.
• 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 T
1 and T
2 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 T
1, T
2, …, T
n.
• 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>;
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
// incomplete type
};
struct Y;
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 |
|
Logical and for Boolean traits B... (since C++17) |
|
Logical or for Boolean traits B... (since C++17) |
|
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.
The C++ standard library provides a few other utilities that are broadly useful to write portable generic code.
Trait |
Effect |
|
yields an “object” (rvalue reference) of a type without constructing it |
|
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.