This paper represents minor corrections to N2028. More motivation for these changes has also been included.
Here is a concise list of problems with the current WP definition of the type traits library. The list is ordered most important first.
Below is a comprehensive proposed fix to all of these problems.
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 is a non-normative change motivated by an attempt to simplify the presentation by putting related traits next to each other in the standard. For example this paper puts all of the array-related traits together, and all of the alignment-related traits together. It also eliminates the "other" category as it is not descriptive of what it contains.
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 Scalar properties and transformations 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
As can be seen above, the organization of the first three sections has remained unchanged. The Implementation requirements section has been dropped as it is no longer desired (this is where the "permission to get it wrong" was). The General Requirements section has been dropped because this was essentially just a list of the tables. However this section did specify which traits met which trait concept. This information has been moved to the individual sections to which they apply. The remaining sections have simply been re-grouped, and in some cases renamed to something more descriptive. For example Primary Type Categories became Primary classification traits.
Additionally this paper proposes reformatting the tabular presentation into paragraphs as is done in other parts of the standard (e.g. as in 20.5 [function.objects]). The motivation for this change is to allow more room for more detailed descriptions of the traits, and accompanying examples where appropriate.
Despite the proposed formatting changes, every effort has been made to presever the existing wording of the current working paper as much as possible. Wording changes are proposed only where:
Where parts of a paragraph are changed by this proposal, insert/
delete highlighting is used to help clarify what is being changed
and what isn't.
Text that appears in this format supplies additional motivation for the proposed changes. This format is used so that these motivating statements are not mistaken for proposed wording.
This subclause 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.
The reference to Table 36 has been removed. The description of the contents of this section has been reworded in an attempt to be more descriptive. There are no normative changes in this section.
A UnaryTypeTrait is a class 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
publicly derived, directly or indirectly, from an instance
a specialization 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 class 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 publicly derived, directly or
indirectly, from an instance
a specialization 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 adapts 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 adaptation. It shall define a nested type
named type, which shall be a synonym for the modified adapted type.
The CopyConstructible requirement has been added to UnaryTypeTrait and BinaryTypeTrait as this is key to using these types for tag dispatching. Also the derivation has been specified to be public which is also important for tag dispatching.
TransformationTrait was inconsistently spelled throughout 20.4 (two different spellings). I picked one spelling and considered the other a type-o, corrected above. The TransformationTrait concept has also been slightly expanded such that aligned_storage can now be considered to meet this concept. Without this change aligned_storage does not meet any of the three traits concepts listed above.
"Modifies" has been changed to "adapts". The former implies that something changes, yet nothing changes via these traits. Instead, a new type is created that is a variation of an existing type.
Rewording generously donated by Jens.
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; // Scalar properties and transformations: 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.
The synopsis has simply been reordered and re-commented to reflect the proposed organization. The proposed changes for the has_trivial/nothrow_* traits are highlighted here. It is important that the names of these traits work well as a group (relative to the names of all similar traits). The synopsis is a good place to see all of the names at once, making it easier to decide what names might be more appropriate.
It is feared that using constructor in the name results in an ambiguous name. Does constructor refer to the default constructor or to the copy constructor, or possibly even both? Using default instead of constructor appears to disambiguate the meaning.
The sentence under the synopsis is taken from the current wording verbatim, but moved. In the current WP wording this sentence appears in two places: Primary Type Categories and Composite type traits. It has been moved here to apply to all of the type traits. Clients should not specialize any trait in the type traits library. For TR1 it was appropriate to allow the client to specialize some traits only because there was a lack of compiler support for those traits. Client specialization was the only workaround.
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 satisfy the UnaryTypeTrait requirements.
The discussion of the purpose of these traits has been changed to match the organization. These traits classify types as opposed to get properties of types. The sentence in the WP referring to the implicit instantiation of a client-supplied type has been moved from this section to 20.4.5 Type properties and transformations as it no longer applies to any trait in this section.
Also a note was added (or moved from the deleted General Requirements section) that these traits fit the UnaryTypeTrait concept.
template <class T> struct is_void : public integral_constant<bool, b> {};
If T is a cv void type then b is true, else it is false.
template <class T> struct is_integral : public integral_constant<bool, b> {};
If T is an cv
integral type ([basic.fundamental]) then b is
true, else it is false.
template <class T> struct is_floating_point : public integral_constant<bool, b> {};
If T is a cv floating point type ([basic.fundamental]) 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 ([basic.compound]) of known or unknown extent then b is true, else it is false. [Note: Class template array ([23.2.1]) is not an array type. -- end note]
template <class T> struct is_pointer : public integral_constant<bool, b> {};
If T is a cv
pointer type ([basic.compound]) then b is
true, else it is false. [Note: Pointer types
Iincludes function
pointers pointers to functions, but not pointers to
non-static members. -- end note]
template <class T> struct is_lvalue_reference : public integral_constant<bool, b> {};
If T is an lvalue reference type ([dcl.ref]) 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 ([dcl.ref]) 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 cv pointer to non-static data member ([basic.compound]) 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 cv pointer to non-static member function ([basic.compound]) then b is true, else it is false.
template <class T> struct is_enum : public integral_constant<bool, b> {};
If T is an cv
enumeration type ([basic.compound]) then
b is true, else it is false.
template <class T> struct is_union : public integral_constant<bool, b> {};
If T is a cv union type ([basic.compound]) then b is true, else it is false.
template <class T> struct is_class : public integral_constant<bool, b> {};
If T is a cv
class type, but and not a union type
([basic.compound]) then b is true, else it
is false.
template <class T> struct is_function : public integral_constant<bool, b> {};
If T is a function type ([basic.compound]) then b is true, else it is false. [Note: Pointer to (non-member) function types are pointers, not functions, and references to functions are references, not functions. -- end note]
Each trait is individually specified to publicly derive from integral_constant<bool, b>. This is a non-normative change from the WP. Everywhere else in the standard we consistently show the base class for each individual definition (e.g. see 20.5.6 [arithmetic.operations]).
Words have also been inserted for each trait concerning the effects of cv-qualifiers. This is also a non-normative change from the WP. It is felt that including this information directly in the definition of each trait it applies to is less error prone than blanket statements since not all traits in 20.4 behave this way. It can become confusing to the uninitiated which traits ignore cv-qualifiers and which don't.
is_pointer clarified to answer true for pointers to static members. This is a normative change.
is_member_object_pointer changed to answer false for pointers to static members. This is a normative change.
is_member_function_pointer changed to answer false for pointers to static members. This is a normative change.
is_reference has been moved to the secondary classification traits section. is_lvalue_reference and is_rvalue_reference have been added as primary classification traits. This is a normative change.
template <class T> struct is_reference : public integral_constant<bool, b> {};
T is a reference type ([basic.fundamental])
b equals is_lvalue_reference<T>::value ||
is_rvalue_reference<T>::value.
template <class T> struct is_arithmetic : public integral_constant<bool, b> {};
T is an arithmetic type ([basic.fundamental])
b equals is_integral<T>::value || is_floating_point<T>::value.
template <class T> struct is_fundamental : public integral_constant<bool, b> {};
T is an fundamental type ([basic.fundamental])
b equals is_void<T>::value || is_arithmetic<T>::value.
template <class T> struct is_member_pointer : public integral_constant<bool, b> {};
T is a pointer to a member or member function
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> {};
T is a scalar type ([basic.types])
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> {};
T is an object type ([basic.types])
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> {};
T is a compound type ([basic.compound])
b equals !is_fundamental<T>::value.
is_reference has been moved from the Primary classification traits to here, and had its definition modified appropriately.
The main change in this section is to specify the definition of each trait in terms of the primary traits. This is intended to be a non-normative change from the WP. The reason this specification is better is because it leaves no doubt what the intended definitions are, or how they react with cv-qualified types. These definitions are simpler, more concise, and less prone to be misinterpreted than the definitions in the current WP.
The change for is_member_pointer is normative. The current WP definition includes pointers to static members.
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.
The second sentence above (regarding implicit instantiation) has been moved from 20.4.5 to here as it applies only to traits in this section.
Those traits whose name begins with is_ satisfy the UnaryTypeTrait requirements. The remaining traits in this section satisfy the TransformationTrait requirements.
template <class T> struct is_const : public integral_constant<bool, b> {};
If T is const-qualified ([basic.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 is_const<int[3]>::value // false is_const<const int[3]>::value // true
-- end example]
template <class T> struct is_volatile : public integral_constant<bool, b> {};
If T is volatile-qualified ([basic.qualifier]) then b is true, else it is false.
template <class T> struct remove_const {typedef implementation type;};
The member typedef type shall be the same type as
T except that any top-level const- qualifier has been removed.
[Example:
remove_const<const volatile int>::type // volatile int remove_const<const int* const>::type // const int* remove_const<const int&>::type // const int& remove_const<const int[3]>::type // int[3]
-- end example]
template <class T> struct remove_volatile {typedef implementation type;};
The member typedef type shall be the same type as
T except that any top-level volatile-
qualifier has been removed. [Example:
remove_volatile<const volatile int>::type evaluates to
const int, whereas remove_volatile<volatile
int*> is volatile int*. Ñ end example ]
template <class T> struct remove_cv {typedef implementation type;};
The member typedef type shall be the same as
T except that any top-level cv-qualifier has been removed.
designate the cv-unqualified version of T.
[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 type shall be the
same type as 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 type shall be
the same type as T volatile.
template <class T> struct add_cv {typedef implementation type;};
The member typedef type shall be the same type as add_const<typename add_volatile<T>::type>::type.
All of the traits regarding inspection and manipulation of cv-qualifiers can be found in this section. This is a non-normative reorganization from the WP where these traits were in sections 20.4.5.3 and 20.4.7.1. It is believed that grouping these traits together in the same section will be clearer to the reader.
Examples have been added to is_const which clarify what has proven to be a confusing point (regarding treatment of arrays).
Jens provided new wording for remove_cv.
The examples for remove_const and remove_cv have been included from the WP, and added to, and are far more readable out of the tabular format as shown herein. I added one extra example to remove_const to clarify arrays. I removed the examples from remove_volatile as they seemed redundant.
A hypen has been added to "top-level".
Minor word-smithing for add_const, and add_volatile. The original is a little terse.
No changes are recommended for add_cv.
The traits in this section satisfy the TransformationTrait requirements.
template <class T> struct remove_reference {typedef implementation type;};
The member typedef type shall be the same type as T,
except that any reference qualifier has been removed.
If T has type "reference to T1", then the member
typedef shall designate T1, otherwise it shall designate T.
template <class T> struct add_reference {typedef implementation type;};
If T is a reference type, then the member typedef
type shall be T, otherwise T&.
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.
Jens provided wording for remove_reference.
add_reference has been removed and replaced with add_lvalue_reference and add_rvalue_reference. The definition of these two have been altered (from the WP's add_reference) so that using them with void types is well defined. This is important as use with void types is one of the main motivating use cases of these traits. Allowing for void is also consistent with the reference implementation in boost.
The traits in this section satisfy the TransformationTrait requirements.
template <class T> struct remove_pointer {typedef implementation type;};
The member typedef type shall be the same as T, except any
top level indirection has been removed. If T is a
cv pointer type A*, the
member typedef type shall be the same as 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;};
The member typedef type shall be the same type as
remove_reference<T>::type*, if T is a
reference type, otherwise T*.
The definition of remove_pointer has been reworded to avoid the term "indirection". This is consistent with core issue 342.
The definition of add_pointer has been truncated. In the current WP, everything from "if" on is redundant.
The traits is_signed and is_unsigned satisfy the UnaryTypeTrait requirements. The traits make_signed and make_unsigned satisfy the TransformationTrait requirements.
template <class T> struct is_signed : public integral_constant<bool, b> {};
T is a signed integral type ([basic.fundamental])
If is_arithmetic<T>::value or is_enum<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> {};
T is an unsigned integral type ([basic.fundamental])
If is_arithmetic<T>::value or is_enum<T>::value is
true, and if T(-1) > T(0) then b is
true, else it is false.
template <class T> struct make_signed {typedef implementation type;};
The member typedef type shall be a signed integral type with the same cv-qualification as T. When T is a signed integral type, the member typedef type shall be the same type as T. When T is an unsigned integral type, the member typedef type shall be the corresponding signed integral type. Otherwise the member typedef type shall be the signed integer type with smallest rank (4.13 [conv.rank]) for which sizeof(T) == sizeof(type).
Requires: T is a cv integral type or enumeration, but not a bool type.
template <class T> struct make_unsigned {typedef implementation type;};
The member typedef type shall be an unsigned integral type with the same cv-qualification as T. When T is an unsigned integral type, the member typedef type shall be the same type as T. When T is a signed integral type, the member typedef type shall be the corresponding unsigned integral type. Otherwise the member typedef type shall be the unsigned integer type with smallest rank (4.13 [conv.rank]) for which sizeof(T) == sizeof(type).
Requires: T is a cv integral type or enumeration, but not a bool type.
The definition of is_signed and is_unsigned have been clarified to work with all integral types (including char, bool, etc.). This much is a clarification of intent from the WP. Furthermore a suggested change in semantics is to have these traits work for enums as well. Note that this definition leaves no question as to how these traits will react to cv-qualified types. The current definition in the WP is not clear on this matter (possibly a normative change, I'm not sure, but it is what is desired).
Additionally floating point types have been declared to work with these traits. The motivation is for consistency with numeric_traits. Invariant:
std::is_signed<T>::value == std::numeric_limits<T>::is_signedfor all arithmetic T.
Two new traits have been added: make_signed and make_unsigned. These traits have been found to be useful in practice and reinvented multiple times. The multiple implementations in the field tend to have different behaviors in the corner cases. This specification standardizes these traits, even for the corner cases.
Invariants for make_signed and make_unsigned:
Let S be make_signed<T>::type and U be make_unsigned<T>::type.
- is_signed<S>::value
- is_unsigned<U>::value
- sizeof(T) == sizeof(S)
- sizeof(T) == sizeof(U)
- !is_same<S, U>::value
- is_convertible<S, U>::value
- is_convertible<U, S>::value
- numeric_limits<U>::digits == numeric_limits<S>::digits + 1
- If is_integral<T>::value and is_signed<T>::value then
- numeric_limits<T>::digits == numeric_limits<S>::digits, else
- numeric_limits<T>::digits == numeric_limits<U>::digits.
The traits rank and extent satisfy the UnaryTypeTrait requirements. The traits remove_extent and remove_all_extents satisfy the TransformationTrait requirements.
template <class T> struct rank : public integral_constant<size_t, v> {};
An integer value representing the rank of objects of type
T ([dcl.array]). [Note: The term "rank" here is used to
describe the number of dimensions of an array type. -- end note]
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> {};
An integer value representing the extent (dimension) of the
I'th bound of objects of type T (8.3.4).
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 extent of U," then
value v shall
evaluate to be zero.; otherwise
value shall evaluate to the number of elements in the
I'th array bound of T.
[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 a type "array of U", the member typedef type shall be U, otherwise T. [Note: For multidimensional arrays, only the first array dimension is removed. For a type "array of const U", the resulting type is const U. --end note]
[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]
All of the traits regarding arrays (except for is_array which is a primary classification trait) can be found in this section. This is a non-normative reorganization from the WP where these traits were in sections 20.4.5.3 and 20.4.7.3. It is believed that grouping these traits together in the same section will be clearer to the reader.
rank of non-array types is specified to be 0 (this is missing from the WP, a normative change). The examples from the WP are preserved here but now fall closer to the definition of rank due to the non-table formatting.
The wording of the definition of extent has been edited for clarity, but there are no normative changes. The examples have been preserved. Note that in the current WP the reader has to scroll back 4 pages to discover what the base class of rank and extent are.
Jens provided wording for remove_extent.
There are no changes at all for remove_all_extents.
The traits in this section satisfy the UnaryTypeTrait requirements.
template <class T> struct is_pod : public integral_constant<bool, b> {};
If T is a cv POD type ([basic.types]) then b is true, else it is false.
Requires: T shall be a complete type, an array type of unknown bounds, or is a void type.
template <class T> struct is_empty : public integral_constant<bool, b> {};
T is an empty class ([class])
If is_class<T>::value is false then b is
false. Otherwise T is considered empty if and only if:
Requires: T shall be a complete type, an array type of unknown bounds, or is a void type.
template <class T> struct is_polymorphic : public integral_constant<bool, b> {};
If T is a polymorphic class [class.virtual] then b is true, else it is false.
Requires: T shall be a complete type, an array type of unknown bounds, or is a void type.
template <class T> struct is_abstract : public integral_constant<bool, b> {};
If T is an abstract class [class.abstract] then b is true, else it is false.
Requires: T shall be a complete type, an array type of unknown bounds, or is a void type.
template <class T> struct has_trivial_defaultconstructor: public integral_constant<bool, b> {};
The default constructor for T is trivial ([class.ctor])
If is_pod<T>::value is true then b is
true, else if T is a cv class or
union type (or array thereof) with a trivial default constructor ([class.ctor]) then
b is true, else it is false.
Requires: T shall be a complete type, an array type of unknown bounds, or is a void type.
template <class T> struct has_trivial_copy : public integral_constant<bool, b> {};
The copy constructor for T is trivial ([class.copy])
If is_pod<T>::value or is_reference<T>::value is
true then b is true, else if T is a
cv class or union type with a trivial copy
constructor ([class.copy]) then b is true, else it is false.
Requires: T shall be a complete type, an array type of unknown bounds, or is a void type.
template <class T> struct has_trivial_assign : public integral_constant<bool, b> {};
The assignment operator for T is trivial ([class.copy])
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 cv
class or union type with a trivial copy assignment ([class.copy]) then
b is true, else it is false.
Requires: T shall be a complete type, an array type of unknown bounds, or is a void type.
template <class T> struct has_trivial_destructor : public integral_constant<bool, b> {};
The destructor for T is trivial ([class.dtor])
If is_pod<T>::value or is_reference<T>::value is
true then b is true, else if T is a
cv class or union type (or array thereof) with a
trivial destructor ([class.dtor]) then b is true, else it is
false.
Requires: T shall be a complete type, an array type of unknown bounds, or is a void type.
template <class T> struct has_nothrow_defaultconstructor: public integral_constant<bool, b> {};
The default constructor for T has an empty exception
specification or can otherwise be deduced never to throw an exception
If has_trivial_default<T>::value is true then
b is true, else if T is a cv
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 shall be a complete type, an array type of unknown bounds, or is a void type.
template <class T> struct has_nothrow_copy : public integral_constant<bool, b> {};
The copy constructor for T has an empty exception
specification or can otherwise be deduced never to throw an
exception
If has_trivial_copy<T>::value is true then
b is true, else if T is a cv
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 shall be a complete type, an array type of unknown bounds, or is a void type.
template <class T> struct has_nothrow_assign : public integral_constant<bool, b> {};
The assignment operator for T has an empty exception
specification or can otherwise be deduced never to throw an exception
If has_trivial_assign<T>::value is true then
b is true, else if T is a cv
class or union type with a copy 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 copy assignment operator must be both known not to throw
an exception, and const qualified, for b to be
true.
Requires: T shall be a complete type, an array type of unknown bounds, or is a void type.
template <class T> struct has_virtual_destructor : public integral_constant<bool, b> {};
If T has is a class type with
a virtual destructor ([class.dtor]) then b is
true, else it is false.
Requires: T shall be a complete type, an array type of unknown bounds, or is a void type.
These 12 traits all deal with inspecting certain member functions (or the lack thereof) in types. Therefore they have been put in this section titled Member introspection. In the current WP they are all found in 20.4.5.3 Type properties along with the is_const, is_volatile, is_signed, and is_unsigned traits.
All of the traits in this section have been extended to work with arrays of unknown bounds, and with void types (a normative change). This is important for ease in meta-programming. Otherwise programmers have to weed out these cases indivdually which is tedious and error prone.
Just in case there is any question, is_pod has been clarified to work with cv-qualified types identically as it would with cv-unqualified types.
The is_empty specification in the WP is inadequate. The definition of an empty class is not well specified in the WP. The definition proposed herein attempts to specify exactly what an empty class is.
has_trivial_default is renamed from has_trivial_constructor and the definition has been changed to explicitly include what happens when given non-class types (such as void or scalars), and cv-qualified types. Array types are specifically mentioned in the definition for clarity as well. This is considerably better specified than the current WP, and consistent with the use cases for this trait. This is arguably a normative change, but I'm not positive. However I am positive it is a desired change consistent with the reference implementation at boost.
has_trivial_copy has been specified to be true for all pod-types and reference types, as well as cv-qualified class types with trivial copy constructors. This is important for the use cases this trait is commonly used (optimization by memcpy for example). This is likely a normative change.
has_trivial_assign has been specified to be false for const qualified types and reference types. Otherwise true for pod types. And then check for trivial assigment operators in class types. This is considerably better specified than the current WP, and consistent with the use cases for this trait. This is a normative change.
has_trivial_destructor has been specified to be true for pod and reference types. And then check for trivial destructors in class types (or arrays thereof). This is considerably better specified than the current WP, and consistent with the use cases for this trait. This is a normative change.
has_nothrow_default has been specified to be true for pod types. And then check for nothrow default constructors in class types (or arrays thereof). This is considerably better specified than the current WP, and consistent with the use cases for this trait. This is a normative change.
has_nothrow_copy has been specified to be true for all types for which has_trivial_copy is also true. And then check for nothrow copy constructors in class types. This is considerably better specified than the current WP, and consistent with the use cases for this trait. This is a normative change.
has_nothrow_assign has been given a much more detailed specification which results in normative changes. For example it can be different in the way it treats const types.
The traits in this section satisfy the BinaryTypeTrait requirements.
template <class T, class U> struct is_same : public integral_constant<bool, b> {};
If T and U name 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 a base class of Derived
([class.derived]) or Base and Derived name the
same type then b is true, otherwise
it is false. Top-level cv-qualifiers of Base and
Derived are ignored. For the purposes of
this trait, a class type (20.4.4.1 [Primary classification traits]) is
considered its own base.
[Note: is_base_of does consider private, protected and ambiguous base classes as bases. --end note]
[Example:
struct B {}; struct B1 : B {}; struct B2 : B {}; struct D : private B1, private B2 {}; is_base_of<B, D>::value // true is_base_of<const B, D>::value // true is_base_of<B, const D>::value // true is_base_of<B, const B>::value // true is_base_of<D, B>::value // false is_base_of<B&, D&>::value // false is_base_of<B[3], D[3]>::value // false is_base_of<int, int>::value // false
[--end example]
Requires: If Base and Derived are class types (20.4.4.1 [Primary classification traits]), and are not the same type (disregarding cv-qualifiers), Derived shall be a complete type. A diagnostic is required if this requirement is not met.
template <class From, class To> struct is_convertible : public integral_constant<bool, b> {};
An imaginary lvalue of type From is implicitly
convertible to type To
([conv])
Special conversions involving
string-literals and null-pointer
constants are not considered ([conv.array], [conv.ptr], and
[conv.mem]). No function-parameter
adjustments ([dcl.fct]) are made to
type To when determining whether
From is convertible to To; this
implies that if type To is a function
type or an array type, then the
condition is false.
See below.
The expression is_convertible<From,To>::value is ill-formed if:
Except that:
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]
Requires: From and To are complete types, arrays of unknown bounds, or void types.
The organization of this section is unchanged from the WP. It has the same name and contents. But it does fall under a different parent section.
is_same is clarified as to how it works with cv-qualifications. Some may view this as a normative change and some may not. Thus clarification is critical.
The changes for is_base_of are normative. This proposal clarifies that it must work with public, protected and private derivation, as well as with multiple inheritance. These words also specify how cv-qualifiers are handled.
There are those that would much prefer the name is_base_and_derived instead of is_base_of. And there are those who strongly feel the opposite. I have no strong feelings either way. I am simply noting the issue here.
The changes for is_convertible are normative.
Given a type T, the TR1 is_convertible<T, T&>::value is true whereas this paper proposes false unless T is a non-volatile but const qualified type. There is good motivation for this change.
Consider this code:
template <class T> class container { public: ... template <class U> typename where < is_convertible<U, T>::value, iterator >::type insert(const_iterator position, U&& x); };The above is not contrived code. I wrote exactly this code when putting move semantics into std::map. In that case T is a pair<const key_type, mapped_type>, and U is some type that must be convertible to the pair, most notably a pair<key_type, mapped_type> (without the const). The rationale for accepting such (non-const) types is so that values can be efficiently moved into the map.
If an lvalue argument is passed (say of type P), U gets deduced as a P&, else it gets deduced as P. Subsequently is_convertible will be testing P& against T if an lvalue argument was passed, or P against T if an rvalue was passed --- exactly as needed if is_convertible considers its From types to be rvalues!
If instead is_convertible traffics in lvalue From types, then the above code is wrong because it will consider an rvalue P as an lvalue when doing the is_convertible test. To regain the correct semantics, the following rewrite will work:
template <class T> class container { public: ... template <class U> typename where < is_convertible<U&&, T>::value, iterator >::type insert(const_iterator position, U&& x); };Now if an lavlue P is passed, U is deduced as P& and is_convertible will compare a P& && which collapses to P& to T. And if an ravlue P is passed, U is deduced as P and is_convertible will compare a P&& to T.
So either way can be made to work, but the former seems easier to read, easier to understand, and less error prone (things just work with the most natural syntax). That is, you can write the latter and have your code work correctly no matter what is_convertible does with From types (rvalue or lvalue). However if you forget to add the "&&", or if you get confused and can't remember whether U& or U&& is correct, just writing U will give you the correct code only if is_convertible considers From as an rvalue.
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 an lvalue, it is possible to do so using references:
is_convertible<unique_ptr<Derived>&, unique_ptr<Base>>::value; // false, From is lvalueIf 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. Note that this is another normative change from the WP which specifies that any type is convertible to void. The motivation for this change is that it is not useful in practice. Note how the TR1 spec had to make an exception in this area. For the proposed definition, behavior with void types falls out of the definition with no need for exceptions to the rule.
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. It is not convertible to function reference types because the source is considered an rvalue, and one can not have a reference to a const function type. function types are not covertible to a function types because one can not use function types as the return type of a function.
Library implementor's note: Except for the protected/private access checks, and the ambiguity checks, this specification is completely implementable in C++03 (even without rvalue references). However it is intended that this be implemented with compiler help to get the access and ambiguity checks correct.
The trait alignment_of satisfies the UnaryTypeTrait requirements. The trait aligned_storage satisfies the TransformationTrait requirements.
template <class T> struct alignment_of : public integral_constant<size_t, v> {};
An integer value representing 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 ([basic.types]). 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 shall be a complete object type, or a reference type, or an array type of unknown bounds.
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.
[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 a multiple of Align.
-- end note]
This section collects the two alignment-related traits into the same section. In the current WP they are located in 20.4.5.3 [meta.unary.prop] and 20.4.8 [meta.trans.other].
These traits may need to be altered (or eliminated altogether) if/when N2140 gets into the WP.
The proposed wording clarifies how alignment_of works with reference types, and arrays of unknown bounds (which are incomplete types). alignment_of is not required to work with function or void types. Working with arrays of unknown bounds is a normative change.
The defaulted second argument for aligned_storage 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 Walter Brown and Robert Klarer are gratefully acknowledged.