diff --git a/libcxx/include/__type_traits/is_always_bitcastable.h b/libcxx/include/__type_traits/is_always_bitcastable.h --- a/libcxx/include/__type_traits/is_always_bitcastable.h +++ b/libcxx/include/__type_traits/is_always_bitcastable.h @@ -15,7 +15,9 @@ #include <__type_traits/is_object.h> #include <__type_traits/is_same.h> #include <__type_traits/is_trivially_copyable.h> +#include <__type_traits/is_void.h> #include <__type_traits/remove_cv.h> +#include <__type_traits/remove_pointer.h> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header @@ -23,6 +25,23 @@ _LIBCPP_BEGIN_NAMESPACE_STD +template +struct __is_always_bitcastable_pointer { + static const bool value = false; +}; + +template +struct __is_always_bitcastable_pointer<_From, + _To, + __enable_if_t::value && is_pointer<_To>::value> > { + using _UnqualFrom = __remove_cv_t<__remove_pointer_t<_From> >; + using _UnqualTo = __remove_cv_t<__remove_pointer_t<_To> >; + + static const bool value = + (sizeof(_From) == sizeof(_To)) && is_object<_UnqualFrom>::value && is_object<_UnqualTo>::value && + (is_same<_UnqualFrom, _UnqualTo>::value || is_void<_UnqualFrom>::value || is_void<_UnqualTo>::value); +}; + // Checks whether an object of type `From` can always be bit-cast to an object of type `To` and represent a valid value // of type `To`. In other words, `From` and `To` have the same value representation and the set of values of `From` is // a subset of the set of values of `To`. @@ -51,8 +70,10 @@ // In practice, that means: // - all integral types (except `bool`, see below) -- that is, character types and `int` types, both signed and // unsigned... + // - enumerations to integral types... // - as well as arrays of such types... // - ...that have the same size. + // - pointers to the same cv-qualified type or void // // Other trivially-copyable types can't be validly bit-cast outside of their own type: // - floating-point types normally have different sizes and thus aren't bit-castable between each other (fails #1); @@ -62,19 +83,21 @@ // - booleans normally use only a single bit of their object representation; bit-casting an integer to a boolean // will result in a boolean object with an incorrect representation, which is undefined behavior (fails #2). // Bit-casting from a boolean into an integer, however, is valid; - // - enumeration types may have different ranges of possible values (fails #1); + // - enumeration types may have different ranges of possible values (fails #1) + // enumerations to integers of the same size are valid // - for pointers, it is not guaranteed that pointers to different types use the same set of values to represent // addresses, and the conversion results are explicitly unspecified for types with different alignments // (fails #1); // - for structs and unions it is impossible to determine whether the set of values of one of them is a subset of // the other (fails #1); + // - pointers to base classes may have different value representations (e.g. virtual bases) (fails #1) // - there is no need to consider `nullptr_t` for practical purposes. ( sizeof(_From) == sizeof(_To) && - is_integral<_From>::value && + (is_integral<_From>::value || is_enum<_From>::value) && is_integral<_To>::value && !is_same<_UnqualTo, bool>::value - ); + ) || __is_always_bitcastable_pointer<_From, _To>::value; }; _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/test/libcxx/type_traits/is_always_bitcastable.compile.pass.cpp b/libcxx/test/libcxx/type_traits/is_always_bitcastable.compile.pass.cpp --- a/libcxx/test/libcxx/type_traits/is_always_bitcastable.compile.pass.cpp +++ b/libcxx/test/libcxx/type_traits/is_always_bitcastable.compile.pass.cpp @@ -66,12 +66,31 @@ } constexpr void test() { + // lists of integrals with specific size + using integral_8 = meta::type_list; + + using integral_16 = meta::type_list< +#if !defined(TEST_HAS_NO_WIDE_CHARACTERS) && __WCHAR_WIDTH__ == 16 + wchar_t, +#endif + char16_t, + int16_t, + uint16_t>; + + using integral_32 = meta::type_list< +#if !defined(TEST_HAS_NO_WIDE_CHARACTERS) && __WCHAR_WIDTH__ == 32 + wchar_t, +#endif + char32_t, + int32_t, + uint32_t>; + using integral_64 = meta::type_list; + // Arithmetic types. { // Bit-castable arithmetic types. // 8-bit types. - using integral_8 = meta::type_list; using chars = meta::type_list; #if CHAR_BIT == 8 check>(); @@ -81,23 +100,12 @@ #endif // 16-bit types. - using integral_16 = meta::type_list; -#if !defined(TEST_HAS_NO_WIDE_CHARACTERS) && __WCHAR_WIDTH__ == 16 - check>>(); -#else check(); -#endif // 32-bit types. - using integral_32 = meta::type_list; -#if !defined(TEST_HAS_NO_WIDE_CHARACTERS) && __WCHAR_WIDTH__ == 32 - check>>(); -#else check(); -#endif // 64-bit types. - using integral_64 = meta::type_list; check(); // 128-bit types. @@ -149,10 +157,20 @@ check_both_ways, meta::type_list>(); } + // Enumerations to integers + { + enum Enum : std::int32_t { Value }; + check, integral_32>(); + + enum class ScopedEnum : std::int32_t { Value }; + check, integral_32>(); + } + // Pointers. { check>(); - check_both_ways, meta::type_list>(); + check_both_ways, meta::type_list>(); + check>(); check>(); check_both_ways, meta::type_list>(); @@ -167,12 +185,16 @@ int MemFunc2() { return 0; } }; using MemObjPtr1 = decltype(&S::mem_obj1); + using ConstMemObjPtr1 = const int S::*; using MemObjPtr2 = decltype(&S::mem_obj2); using MemFuncPtr1 = decltype(&S::MemFunc1); + using ConstMemFuncPtr1 = void(S::*)() const; using MemFuncPtr2 = decltype(&S::MemFunc2); check>(); + check, meta::type_list>(); check>(); + check, meta::type_list>(); check_both_ways, meta::type_list>(); check_both_ways, meta::type_list>(); }