This paper proposes a few normative changes to the type traits library.
The existing organization is:
20.4 Metaprogramming and type traits 20.4.1 Requirements 20.4.2 Header <type_traits> synopsis 20.4.3 Helper classes 20.4.4 General Requirements 20.4.5 Unary Type Traits 20.4.5.1 Primary Type Categories 20.4.5.1 Composite type traits 20.4.5.3 Type properties 20.4.6 Relationships between types 20.4.7 Transformations between types 20.4.7.1 Const-volatile modifications 20.4.7.2 Reference modifications 20.4.7.3 Array modifications 20.4.7.4 Pointer modifications 20.4.8 Other transformations 20.4.9 Implementation requirements
This paper proposes a slightly modified organization which groups traits more by functionality instead of by concepts. This (for example) puts all of the array-related traits together, and all of the alignment-related traits together. It also eliminates the "other" category.
20.4 Metaprogramming and type traits 20.4.1 Requirements 20.4.2 Header <type_traits> synopsis 20.4.3 Helper classes 20.4.4 Type classification traits 20.4.4.1 Primary classification traits 20.4.4.2 Secondary classification traits 20.4.5 Type properties and transformations 20.4.5.1 Const-volatile properties and transformations 20.4.5.2 Reference transformations 20.4.5.3 Pointer transformations 20.4.5.4 Integral properties 20.4.5.5 Array properties and transformations 20.4.5.6 Member introspection 20.4.5.7 Relationships between types 20.4.5.8 Alignment properties and transformations
Additionally this paper proposes some formatting changes. More in depth specifications are more convenient in a paragraph format than a tabular format.
This sub-clause describes components used by C++ programs, particularly in
templates, to support the widest possible range of types, optimize template code
usage, detect type related user errors, and perform type inference and
transformation at compile time. It describes type traits requirements,
unary type traits, traits that describe relationships between types, and traits
that perform transformations on types, as summarized in Table 36.
Included are type classification traits, type property inspection traits,
and type transformations. The type classification traits describe a complete
taxonomy of all possible C++ types, and state where in that taxonomy a given
type belongs. The type property inspection traits allow important
characteristics of types, or combinations of types, to be inspected. The type
transformations allow certain properties of types to be manipulated.
A UnaryTypeTrait is a template that describes a property of a type. It shall be a class template that takes one template type argument and, optionally, additional arguments that help define the property being described. It shall be DefaultConstructible, CopyConstructible, and derived, directly or indirectly, from an instance of the template integral_constant (20.4.3), with the arguments to the template integral_constant determined by the requirements for the particular property being described.
A BinaryTypeTrait is a template that describes a relationship between two types. It shall be a class template that takes two template type arguments and, optionally, additional arguments that help define the relationship being described. It shall be DefaultConstructible, CopyConstructible, and derived, directly or indirectly, from an instance of the template integral_constant (20.4.3), with the arguments to the template integral_constant determined by the requirements for the particular relationship being described.
A TransformationTypeTrait is a template that modifies a property
of a type. It shall be a class template that takes one or more
template type arguments and, optionally, additional
arguments that help define the modification. It shall define a nested type
named type, which shall be a synonym for the modified type.
namespace std { // helper class: template <class T, T v> struct integral_constant; typedef integral_constant<bool, true> true_type; typedef integral_constant<bool, false> false_type; // Primary classification traits: template <class T> struct is_void; template <class T> struct is_integral; template <class T> struct is_floating_point; template <class T> struct is_array; template <class T> struct is_pointer; template <class T> struct is_reference; template <class T> struct is_member_object_pointer; template <class T> struct is_member_function_pointer; template <class T> struct is_enum; template <class T> struct is_union; template <class T> struct is_class; template <class T> struct is_function; // Secondary classification traits: template <class T> struct is_arithmetic; template <class T> struct is_fundamental; template <class T> struct is_member_pointer; template <class T> struct is_scalar; template <class T> struct is_object; template <class T> struct is_compound; // Const-volatile properties and transformations: template <class T> struct is_const; template <class T> struct is_volatile; template <class T> struct remove_const; template <class T> struct remove_volatile; template <class T> struct remove_cv; template <class T> struct add_const; template <class T> struct add_volatile; template <class T> struct add_cv; // Reference transformations: template <class T> struct remove_reference; template <class T> struct add_reference; // Pointer transformations: template <class T> struct remove_pointer; template <class T> struct add_pointer; // Integral properties: template <class T> struct is_signed; template <class T> struct is_unsigned; // Array properties and transformations: template <class T> struct rank; template <class T, unsigned I = 0> struct extent; template <class T> struct remove_extent; template <class T> struct remove_all_extents; // Member introspection: template <class T> struct is_pod; template <class T> struct is_empty; template <class T> struct is_polymorphic; template <class T> struct is_abstract; template <class T> struct has_trivial_defaulthas_trivial_constructor; template <class T> struct has_trivial_copy; template <class T> struct has_trivial_assign; template <class T> struct has_trivial_destructor; template <class T> struct has_nothrow_defaulthas_nothrow_constructor; template <class T> struct has_nothrow_copy; template <class T> struct has_nothrow_assign; template <class T> struct has_virtual_destructor; // Relationships between types: template <class T, class U> struct is_same; template <class Base, class Derived> struct is_base_of; template <class From, class To> struct is_convertible; // Alignment properties and transformations: template <class T> struct alignment_of; template <size_t Len, size_t Align = most_stringent_alignment_requirement> struct aligned_storage; } // namespace std
The behavior of a program that adds specializations for any of the class templates defined in this clause is undefined.
No changes.
The classification traits describe the C++ taxonomy of types. These traits are grouped into two broad categories: primary and secondary. The primary traits identify the leaves of the taxonomy tree. Every type that can be created in C++ fits into one and only one of the primary categories. The secondary classifications represent combinations of the primary classifications that are useful for determining characteristics of a relatively broad range of types (e.g. is a type a scalar?).
All traits in this section model UnaryTypeTrait.
template <class T> struct is_void : public integral_constant<bool, b> {};
If T is a (possibly cv-qualified) void type then b is true, else it is false.
template <class T> struct is_integral : public integral_constant<bool, b> {};
If T is a (possibly cv-qualified) integral type then b is true, else it is false.
template <class T> struct is_floating_point : public integral_constant<bool, b> {};
If T is a (possibly cv-qualified) floating point type then b is true, else it is false.
template <class T> struct is_array : public integral_constant<bool, b> {};
If T is an array type of known or unknown bounds then b is true, else it is false.
template <class T> struct is_pointer : public integral_constant<bool, b> {};
If T is a (possibly cv-qualified) pointer type then b is true, else it is false. Pointer types include pointers to functions, but not pointers to non-static members.
template <class T> struct is_reference : public integral_constant<bool, b> {};
If T is a reference type then b is true, else it is false.
If rvalue references are accepted into the language then is_reference will become a secondary classification trait, and will be based on is_lvalue_reference and is_rvalue_reference which need to be added:
template <class T> struct is_lvalue_reference : public integral_constant<bool, b> {};If T is an lvalue reference type then b is true, else it is false.
template <class T> struct is_rvalue_reference : public integral_constant<bool, b> {};If T is an rvalue reference type then b is true, else it is false.
template <class T> struct is_member_object_pointer : public integral_constant<bool, b> {};
If T is a (possibly cv-qualified) pointer to non-static member data type then b is true, else it is false.
template <class T> struct is_member_function_pointer : public integral_constant<bool, b> {};
If T is a (possibly cv-qualified) pointer to non-static member function type then b is true, else it is false.
template <class T> struct is_enum : public integral_constant<bool, b> {};
If T is a (possibly cv-qualified) pointer to an enumeration type then b is true, else it is false.
template <class T> struct is_union : public integral_constant<bool, b> {};
If T is a (possibly cv-qualified) union type then b is true, else it is false.
template <class T> struct is_class : public integral_constant<bool, b> {};
If T is a (possibly cv-qualified) class type, and not a union type then b is true, else it is false.
template <class T> struct is_function : public integral_constant<bool, b> {};
If T is a non-member function type then b is true, else it is false. Note that pointer to function types are pointers, not functions, and references to functions are references, not functions.
If rvalue references are accepted into the language then is_reference becomes a secondary classification trait:
template <class T> struct is_reference : public integral_constant<bool, b> {};b equals is_lvalue_reference<T>::value || is_rvalue_reference<T>::value.
template <class T> struct is_arithmetic : public integral_constant<bool, b> {};
b equals is_integral<T>::value || is_floating_point<T>::value.
template <class T> struct is_fundamental : public integral_constant<bool, b> {};
b equals is_void<T>::value || is_arithmetic<T>::value.
template <class T> struct is_member_pointer : public integral_constant<bool, b> {};
b equals is_member_object_pointer<T>::value || is_member_function_pointer<T>::value.
template <class T> struct is_scalar : public integral_constant<bool, b> {};
b equals is_arithmetic<T>::value || is_member_pointer<T>::value || is_pointer<T>::value || is_enum<T>::value.
template <class T> struct is_object : public integral_constant<bool, b> {};
b equals is_scalar<T>::value || is_array<T>::value || is_union<T>::value || is_class<T>::value.
template <class T> struct is_compound : public integral_constant<bool, b> {};
b equals !is_fundamental<T>::value.
These traits allow inspection of certain properties of types, and in some cases computing a type based on the template parameter input.
For all of the class templates X declared in this clause, instantiating that template with a template-argument that is a class template specialization may result in the implicit instantiation of the template argument if and only if the semantics of X require that the argument must be a complete type.
Those traits whose name begins with is_ model UnaryTypeTrait. The remaining traits in this section model TransformationTrait.
template <class T> struct is_const : public integral_constant<bool, b> {};
If T has a top level const qualifier then b is true, else it is false.
[Example:
is_const<const volatile int>::value // true is_const<const int*>::value // false is_const<const int&>::value // false
-- end example]
template <class T> struct is_volatile : public integral_constant<bool, b> {};
If T has a top level volatile qualifier then b is true, else it is false.
template <class T> struct remove_const {typedef implementation type;};
type is the same type as T except that any top level const-qualifier has been removed (if it exists).
[Example:
remove_const<const volatile int>::type // volatile int remove_const<const int* const>::type // const int* remove_const<const int&>::type // const int&
-- end example]
template <class T> struct remove_volatile {typedef implementation type;};
type is the same type as T except that any top level volatile-qualifier has been removed (if it exists).
template <class T> struct remove_cv {typedef implementation type;};
type is the same type as T except that all top level cv-qualifiers have been removed (if they exist).
[Example:
remove_cv<const volatile int>::type // int remove_cv<volatile int* volatile>::type // volatile int* remove_cv<const int&>::type // const int&
-- end example]
template <class T> struct add_const {typedef implementation type;};
If T is a reference, function, or top level const-qualified type, then type shall be the same type as T, otherwise T const.
template <class T> struct add_volatile {typedef implementation type;};
If T is a reference, function, or top level volatile-qualified type, then type shall be the same type as T, otherwise T volatile.
template <class T> struct add_cv {typedef implementation type;};
type shall be the same type as add_const<typename add_volatile<T>::type>::type.
The traits in this section model TransformationTrait.
template <class T> struct remove_reference {typedef implementation type;};
type shall be the same type as T, except that any reference qualifier has been removed.
If the rvalue reference is accepted, the remove_reference description should be changed to:
type shall be the same type as T, except that any lvalue reference or rvalue reference qualifier has been removed.
template <class T> struct add_reference {typedef implementation type;};
For object and function types type shall be the same type as T&. Else type is T.
If the rvalue reference is accepted, add_reference should be removed and replaced with:
template <class T> struct add_lvalue_reference {typedef implementation type;};For object and function types type shall be the same type as T&. For rvalue reference types (e.g. T&&), type shall become the corresponding lvalue reference type (e.g. T&). Else type is T.
template <class T> struct add_rvalue_reference {typedef implementation type;};For object and function types type shall be the same type as T&&. Else type is T.
The traits in this section model TransformationTrait.
template <class T> struct remove_pointer {typedef implementation type;};
If T is a (possibly cv-qualified) pointer type A*, type becomes the pointed to type A, Otherwise type is the same type as T. [Note: pointers to members are left unchanged by remove_pointer.]
template <class T> struct add_pointer {typedef implementation type;};
type shall be the same type as remove_reference<T>::type*.
The traits in this section model UnaryTypeTrait.
template <class T> struct is_signed : public integral_constant<bool, b> {};
If is_integral<T>::value is true, and if T(-1) < T(0) then b is true, else it is false.
template <class T> struct is_unsigned : public integral_constant<bool, b> {};
If is_integral<T>::value is true, and if T(-1) > T(0) then b is true, else it is false.
The traits rank and extent model UnaryTypeTrait. The traits remove_extent and remove_all_extents model TransformationTrait.
template <class T> struct rank : public integral_constant<size_t, v> {};
v is the number of dimensions of the type T. For types T which are not array types v is 0.
[Example:
// the following assertions hold: static_assert(rank<int>::value == 0, ""); static_assert(rank<int[2]>::value == 1, ""); static_assert(rank<int[][4]>::value == 2, "");
-- end example]
template <class T, unsigned I = 0> struct extent : public integral_constant<size_t, v> {};
v is the size of the I'th dimension of the type T. If the type T is not an array type, has rank of less than I, or if I == 0 and T is of type "array of unknown bound of U," then v is 0.
[Note: The term "extent" here is used to describe the number of elements in an array type -- end note]
[Example:
// the following assertions hold: static_assert(extent<int>::value == 0, ""); static_assert(extent<int[2]>::value == 2, ""); static_assert(extent<int[2][4]>::value == 2, ""); static_assert(extent<int[][4]>::value == 0, ""); static_assert((extent<int, 1>::value) == 0, ""); static_assert((extent<int[2], 1>::value) == 0, ""); static_assert((extent<int[2][4], 1>::value) == 4, ""); static_assert((extent<int[][4], 1>::value) == 4, "");
-- end example]
template <class T> struct remove_extent {typedef implementation type;};
If T is "array of U", the member type shall be U, otherwise T. For multidimensional arrays, only the first (leftmost) array dimension is removed. For a type "array of const U", the resulting type is const U.
[Example:
// the following assertions hold: static_assert((is_same<remove_extent<int>::type, int>::value), ""); static_assert((is_same<remove_extent<int[2]>::type, int>::value), ""); static_assert((is_same<remove_extent<int[2][3]>::type, int[3]>::value), ""); static_assert((is_same<remove_extent<int[][3]>::type, int[3]>::value), "");
-- end example]
template <class T> struct remove_all_extents {typedef implementation type;};
If T is "multi-dimensional array of U", the member type shall be U, otherwise T.
[Example:
// the following assertions hold: static_assert((is_same<remove_all_extents<int>::type, int>::value), ""); static_assert((is_same<remove_all_extents<int[2]>::type, int>::value), ""); static_assert((is_same<remove_all_extents<int[2][3]>::type, int>::value), ""); static_assert((is_same<remove_all_extents<int[][3]>::type, int>::value), "");
-- end example]
The traits in this section model UnaryTypeTrait.
template <class T> struct is_pod : public integral_constant<bool, b> {};
If T is a (possibly cv-qualified) pod-type then b is true, else it is false.
Requires: T is a complete type, an array type of unknown bounds of a complete type, or is a void type.
template <class T> struct is_empty : public integral_constant<bool, b> {};
If is_class<T>::value is false then b is false. Otherwise T is considered empty if and only if:
Requires: T is a complete type, an array type of unknown bounds of a complete type, or is a void type.
template <class T> struct is_polymorphic : public integral_constant<bool, b> {};
If T has a virtual function then b is true, else it is false.
Requires: T is a complete type, an array type of unknown bounds of a complete type, or is a void type.
template <class T> struct is_abstract : public integral_constant<bool, b> {};
If T has a pure virtual function then b is true, else it is false.
Requires: T is a complete type, an array type of unknown bounds of a complete type, or is a void type.
template <class T> structhas_trivial_constructorhas_trivial_default : public integral_constant<bool, b> {};
If is_pod<T>::value is true then b is true, else if T is a (possibly cv-qualified) class or union type (or array thereof) with a trivial default constructor then b is true, else it is false.
Requires: T is a complete type, an array type of unknown bounds of a complete type, or is a void type.
template <class T> struct has_trivial_copy : public integral_constant<bool, b> {};
If is_pod<T>::value or is_reference<T>::value is true then b is true, else if T is a (possibly cv-qualified) class or union type with a trivial copy constructor then b is true, else it is false.
Requires: T is a complete type, an array type of unknown bounds of a complete type, or is a void type.
template <class T> struct has_trivial_assign : public integral_constant<bool, b> {};
If is_const<T>::value or is_reference<T>::value is true then b is false. Otherwise if is_pod<T>::value is true then b is true, else if T is a (possibly cv-qualified) class or union type with a trivial copy constructor then b is true, else it is false.
Requires: T is a complete type, an array type of unknown bounds of a complete type, or is a void type.
template <class T> struct has_trivial_destructor : public integral_constant<bool, b> {};
If is_pod<T>::value or is_reference<T>::value is true then b is true, else if T is a (possibly cv-qualified) class or union type (or array thereof) with a trivial destructor then b is true, else it is false.
Requires: T is a complete type, an array type of unknown bounds of a complete type, or is a void type.
template <class T> structhas_nothrow_constructorhas_nothrow_default : public integral_constant<bool, b> {};
If has_trivial_default<T>::value is true then b is true, else if T is a (possibly cv-qualified) class or union type (or array thereof) with a default constructor that is known not to throw an exception then b is true, else it is false.
Requires: T is a complete type, an array type of unknown bounds of a complete type, or is a void type.
template <class T> struct has_nothrow_copy : public integral_constant<bool, b> {};
If has_trivial_copy<T>::value is true then b is true, else if T is a (possibly cv-qualified) class or union type with a copy constructor that is known not to throw an exception then b is true, else it is false.
Requires: T is a complete type, an array type of unknown bounds of a complete type, or is a void type.
template <class T> struct has_nothrow_assign : public integral_constant<bool, b> {};
If has_trivial_assign<T>::value is true then b is true, else if T is a (possibly cv-qualified) class or union type with an assignment operator (taking an lvalue of type T) that is known not to throw an exception then b is true, else it is false. If T is a const type, the assignment operator must be both known not to throw an exception, and const qualified, for b to be true.
Requires: T is a complete type, an array type of unknown bounds of a complete type, or is a void type.
template <class T> struct has_virtual_destructor : public integral_constant<bool, b> {};
If T is a class type with a virtual destructor then b is true, else it is false.
Requires: T is a complete type, an array type of unknown bounds of a complete type, or is a void type.
The traits in this section model BinaryTypeTrait.
template <class T, class U> struct is_same : public integral_constant<bool, b> {};
If T and U are the same type, with identical cv-qualifications, then b is true, else it is false.
template <class Base, class Derived> struct is_base_of : public integral_constant<bool, b> {};
If Base is either the same type as Derived or is a base class of Derived, then b is true, else it is false.
Requires: T is a complete type, an array type of unknown bounds of a complete type, or is a void type.
template <class From, class To> struct is_convertible : public integral_constant<bool, b> {};
If the following test function is well formed code b is true, else it is false.
template <class T> typename add_rvalue_reference<T>::type create(); To test() {return create<From>();}
[Note: This definition gives well defined results for reference types, void types, array types and function types.--end note]
The wording above assumes acceptance of the rvalue reference proposal. However the definition could remain the same, but have more complicated wording without using add_rvalue_reference or requiring rvalue references in the language.
The above definition means that casual use of is_convertible with movable but non-copyable types (which can exist in C++03) is more likely to give an intuitively correct answer. For example:
is_convertible<unique_ptr<Derived>, unique_ptr<Base>>::value; // trueIf the client wishes to force the semantics of From being considered either an lvalue or an rvalue, it is possible to do so using references:
is_convertible<unique_ptr<Derived>&, unique_ptr<Base>>::value; // false, From is lvalue is_convertible<unique_ptr<Derived>&&, unique_ptr<Base>>::value; // true, From is rvalueIf To is an array or function type, the result is false since these types are not allowed to be return types. Note however that these types can be From because of the add_rvalue_reference (you can have references to these types).
If To and From are void types, the result is true (no matter the cv-qualifications). However if only one of To or From is a void type, then the result is false.
If From is an array type then it will be convertible to appropriate To pointer types.
If From is a function type then it will be convertible to appropriate To function pointer types or function reference types, but not to a function type.
Requires: T is a complete type, an array type of unknown bounds of a complete type, or is a void type.
The trait alignment_of models UnaryTypeTrait. The trait aligned_storage models TransformationTrait.
template <class T> struct alignment_of : public integral_constant<size_t, v> {};
v is the number of bytes of the alignment of objects of type T (i.e. the alignment requirement [basic.types]); an object of type T may be allocated at an address that is a multiple of its alignment. If T is a reference type, alignment_of finds the alignment of the reference, not of the referenced type. If T is an array type, alignment_of finds the alignment of the array's element type.
Requires: T is a complete object type, or a reference type, or an array type of unknown bounds of a complete type.
template <size_t Len, size_t Align = most_stringent_alignment_requirement> struct aligned_storage {typedef implementation type;};
The member typedef type shall be a POD type suitable for use as uninitialized storage for any object whose size is at most Len and whose alignment requirement is a divisor of Align. If Align is defaulted, it will be a multiple of the most stringent alignment requirement of any valid C++ object type whose size is no greater than Len [expr.new].
Requires: Len is nonzero. If not defaulted, Align is equal to alignment_of<T>::value for some type T.
Invariant: alignment_of<aligned_storage<Len, Align>::type>::value == Align for any Align that meets the requirements of aligned_storage.
[Note: a typical implementation would define type as:
union type { unsigned char __data[Len]; Aligner __align; };
where Aligner is the smallest POD type for which alignment_of<Aligner>::value is Align.
It is possible that aligned_storage<Len, Align> supports greater values of Align than is implied by the defaulted value of Align, by returning types which are outside of the definition of C++ (e.g. to support page-aligned memory). However the invariant above shall hold in any case. And the client shall be assured that if Align is defaulted, then the type returned by aligned_storage shall have compatible alignment requirements for any C++ object type.
-- end note]
The defaulted second argument allows one to create storage for types of unknown alignment requirements (e.g. as operator new does). The wording "most stringent alignment requirement" is taken from [expr.new].
Excellent editing and suggestions by Robert Klarer are gratefully acknowledged.