[libc++] Implement C++20's P0476R2: std::bit_cast

Thanks to Arthur O'Dwyer for fixing up some of the tests.

Differential Revision: https://reviews.llvm.org/D75960
This commit is contained in:
Louis Dionne 2020-05-28 14:28:38 -04:00 committed by Louis Dionne
parent 8b58ab8ccd
commit b1fb3d75c9
16 changed files with 423 additions and 55 deletions

View File

@ -190,7 +190,7 @@ Status
------------------------------------------------- -----------------
``__cpp_lib_bind_front`` ``201907L``
------------------------------------------------- -----------------
``__cpp_lib_bit_cast`` *unimplemented*
``__cpp_lib_bit_cast`` ``201806L``
------------------------------------------------- -----------------
``__cpp_lib_bitops`` *unimplemented*
------------------------------------------------- -----------------

View File

@ -29,7 +29,7 @@
"`P0019R8 <https://wg21.link/P0019R8>`__","LWG","Atomic Ref","Rapperswil","",""
"`P0458R2 <https://wg21.link/P0458R2>`__","LWG","Checking for Existence of an Element in Associative Containers","Rapperswil","|Complete|","13.0"
"`P0475R1 <https://wg21.link/P0475R1>`__","LWG","LWG 2511: guaranteed copy elision for piecewise construction","Rapperswil","|Complete|",""
"`P0476R2 <https://wg21.link/P0476R2>`__","LWG","Bit-casting object representations","Rapperswil","",""
"`P0476R2 <https://wg21.link/P0476R2>`__","LWG","Bit-casting object representations","Rapperswil","|Complete|","14.0"
"`P0528R3 <https://wg21.link/P0528R3>`__","CWG","The Curious Case of Padding Bits, Featuring Atomic Compare-and-Exchange","Rapperswil","",""
"`P0542R5 <https://wg21.link/P0542R5>`__","CWG","Support for contract based programming in C++","Rapperswil","*Removed in Cologne*","n/a"
"`P0556R3 <https://wg21.link/P0556R3>`__","LWG","Integral power-of-2 operations","Rapperswil","|Complete|","9.0"

1 Paper # Group Paper Name Meeting Status First released version
29 `P0019R8 <https://wg21.link/P0019R8>`__ LWG Atomic Ref Rapperswil
30 `P0458R2 <https://wg21.link/P0458R2>`__ LWG Checking for Existence of an Element in Associative Containers Rapperswil |Complete| 13.0
31 `P0475R1 <https://wg21.link/P0475R1>`__ LWG LWG 2511: guaranteed copy elision for piecewise construction Rapperswil |Complete|
32 `P0476R2 <https://wg21.link/P0476R2>`__ LWG Bit-casting object representations Rapperswil |Complete| 14.0
33 `P0528R3 <https://wg21.link/P0528R3>`__ CWG The Curious Case of Padding Bits, Featuring Atomic Compare-and-Exchange Rapperswil
34 `P0542R5 <https://wg21.link/P0542R5>`__ CWG Support for contract based programming in C++ Rapperswil *Removed in Cologne* n/a
35 `P0556R3 <https://wg21.link/P0556R3>`__ LWG Integral power-of-2 operations Rapperswil |Complete| 9.0

View File

@ -338,6 +338,7 @@ which no dialect declares as such (See the second form described above).
* ``upper_bound``
* ``lock_guard``'s constructors
* ``as_const``
* ``bit_cast``
* ``forward``
* ``move``
* ``move_if_noexcept``

View File

@ -94,6 +94,7 @@ set(files
__algorithm/upper_bound.h
__availability
__bit_reference
__bit/bit_cast.h
__bits
__bsd_locale_defaults.h
__bsd_locale_fallbacks.h

View File

@ -0,0 +1,38 @@
// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef _LIBCPP___BIT_BIT_CAST_H
#define _LIBCPP___BIT_BIT_CAST_H
#include <__config>
#include <type_traits>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
#endif
_LIBCPP_BEGIN_NAMESPACE_STD
#if _LIBCPP_STD_VER > 17
template<class _ToType, class _FromType, class = enable_if_t<
sizeof(_ToType) == sizeof(_FromType) &&
is_trivially_copyable_v<_ToType> &&
is_trivially_copyable_v<_FromType>
>>
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI
constexpr _ToType bit_cast(_FromType const& __from) noexcept {
return __builtin_bit_cast(_ToType, __from);
}
#endif // _LIBCPP_STD_VER > 17
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP___BIT_BIT_CAST_H

View File

@ -14,6 +14,9 @@
bit synopsis
namespace std {
// [bit.cast], bit_cast
template<class To, class From>
constexpr To bit_cast(const From& from) noexcept; // C++20
// [bit.pow.two], integral powers of 2
template <class T>
@ -54,8 +57,9 @@ namespace std {
*/
#include <__config>
#include <__bit/bit_cast.h>
#include <__bits> // __libcpp_clz
#include <__config>
#include <__debug>
#include <limits>
#include <type_traits>

View File

@ -335,6 +335,10 @@ module std [system] {
module bit {
header "bit"
export *
module __bit {
module bit_cast { private header "__bit/bit_cast.h" }
}
}
module bitset {
header "bitset"

View File

@ -286,7 +286,7 @@ __cpp_lib_void_t 201411L <type_traits>
# define __cpp_lib_barrier 201907L
# endif
# define __cpp_lib_bind_front 201907L
// # define __cpp_lib_bit_cast 201806L
# define __cpp_lib_bit_cast 201806L
// # define __cpp_lib_bitops 201907L
# define __cpp_lib_bounded_array_traits 201902L
# if !defined(_LIBCPP_HAS_NO_CHAR8_T)

View File

@ -0,0 +1,16 @@
// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// REQUIRES: modules-build
// WARNING: This test was generated by 'generate_private_header_tests.py'
// and should not be edited manually.
// expected-error@*:* {{use of private header from outside its module: '__bit/bit_cast.h'}}
#include <__bit/bit_cast.h>

View File

@ -18,6 +18,7 @@
// be listed in `UsingLibcxx.rst` in the documentation for the extension.
#include <algorithm>
#include <bit> // bit_cast
#include <cstddef> // to_integer
#include <functional> // identity
#include <iterator>
@ -165,13 +166,13 @@ void test_template_cast_wrappers(LV&& lv, RV&& rv) {
void test_nontemplate_cast_wrappers()
{
#if TEST_STD_VER >= 17
#if TEST_STD_VER > 14
std::byte b{42};
std::to_integer<int>(b);
#endif
#if TEST_STD_VER >= 20
// std::bit_cast<unsigned int>(42);
#if TEST_STD_VER > 17
std::bit_cast<unsigned int>(42);
#endif
#if TEST_STD_VER > 20

View File

@ -19,6 +19,7 @@
// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_NODISCARD
#include <algorithm>
#include <bit> // bit_cast
#include <cstddef> // to_integer
#include <functional> // identity
#include <iterator>
@ -323,14 +324,15 @@ void test_template_cast_wrappers(LV&& lv, RV&& rv) {
void test_nontemplate_cast_wrappers()
{
#if TEST_STD_VER >= 17
#if TEST_STD_VER > 14
std::byte b{42};
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
std::to_integer<int>(b);
#endif
#if TEST_STD_VER >= 20
// std::bit_cast<unsigned int>(42);
#if TEST_STD_VER > 17
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
std::bit_cast<unsigned int>(42);
#endif
#if TEST_STD_VER > 20

View File

@ -81,17 +81,11 @@
#elif TEST_STD_VER == 20
# if !defined(_LIBCPP_VERSION)
# ifndef __cpp_lib_bit_cast
# error "__cpp_lib_bit_cast should be defined in c++20"
# endif
# if __cpp_lib_bit_cast != 201806L
# error "__cpp_lib_bit_cast should have the value 201806L in c++20"
# endif
# else // _LIBCPP_VERSION
# ifdef __cpp_lib_bit_cast
# error "__cpp_lib_bit_cast should not be defined because it is unimplemented in libc++!"
# endif
# ifndef __cpp_lib_bit_cast
# error "__cpp_lib_bit_cast should be defined in c++20"
# endif
# if __cpp_lib_bit_cast != 201806L
# error "__cpp_lib_bit_cast should have the value 201806L in c++20"
# endif
# if !defined(_LIBCPP_VERSION)
@ -123,17 +117,11 @@
#elif TEST_STD_VER > 20
# if !defined(_LIBCPP_VERSION)
# ifndef __cpp_lib_bit_cast
# error "__cpp_lib_bit_cast should be defined in c++2b"
# endif
# if __cpp_lib_bit_cast != 201806L
# error "__cpp_lib_bit_cast should have the value 201806L in c++2b"
# endif
# else // _LIBCPP_VERSION
# ifdef __cpp_lib_bit_cast
# error "__cpp_lib_bit_cast should not be defined because it is unimplemented in libc++!"
# endif
# ifndef __cpp_lib_bit_cast
# error "__cpp_lib_bit_cast should be defined in c++2b"
# endif
# if __cpp_lib_bit_cast != 201806L
# error "__cpp_lib_bit_cast should have the value 201806L in c++2b"
# endif
# if !defined(_LIBCPP_VERSION)

View File

@ -2264,17 +2264,11 @@
# error "__cpp_lib_bind_front should have the value 201907L in c++20"
# endif
# if !defined(_LIBCPP_VERSION)
# ifndef __cpp_lib_bit_cast
# error "__cpp_lib_bit_cast should be defined in c++20"
# endif
# if __cpp_lib_bit_cast != 201806L
# error "__cpp_lib_bit_cast should have the value 201806L in c++20"
# endif
# else // _LIBCPP_VERSION
# ifdef __cpp_lib_bit_cast
# error "__cpp_lib_bit_cast should not be defined because it is unimplemented in libc++!"
# endif
# ifndef __cpp_lib_bit_cast
# error "__cpp_lib_bit_cast should be defined in c++20"
# endif
# if __cpp_lib_bit_cast != 201806L
# error "__cpp_lib_bit_cast should have the value 201806L in c++20"
# endif
# if !defined(_LIBCPP_VERSION)
@ -3421,17 +3415,11 @@
# error "__cpp_lib_bind_front should have the value 201907L in c++2b"
# endif
# if !defined(_LIBCPP_VERSION)
# ifndef __cpp_lib_bit_cast
# error "__cpp_lib_bit_cast should be defined in c++2b"
# endif
# if __cpp_lib_bit_cast != 201806L
# error "__cpp_lib_bit_cast should have the value 201806L in c++2b"
# endif
# else // _LIBCPP_VERSION
# ifdef __cpp_lib_bit_cast
# error "__cpp_lib_bit_cast should not be defined because it is unimplemented in libc++!"
# endif
# ifndef __cpp_lib_bit_cast
# error "__cpp_lib_bit_cast should be defined in c++2b"
# endif
# if __cpp_lib_bit_cast != 201806L
# error "__cpp_lib_bit_cast should have the value 201806L in c++2b"
# endif
# if !defined(_LIBCPP_VERSION)

View File

@ -0,0 +1,63 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-no-concepts
// <bit>
//
// template<class To, class From>
// constexpr To bit_cast(const From& from) noexcept; // C++20
// This test makes sure that std::bit_cast fails when any of the following
// constraints are violated:
//
// (1.1) sizeof(To) == sizeof(From) is true;
// (1.2) is_trivially_copyable_v<To> is true;
// (1.3) is_trivially_copyable_v<From> is true.
//
// Also check that it's ill-formed when the return type would be
// ill-formed, even though that is not explicitly mentioned in the
// specification (but it can be inferred from the synopsis).
#include <bit>
#include <concepts>
template<class To, class From>
concept bit_cast_is_valid = requires(From from) {
{ std::bit_cast<To>(from) } -> std::same_as<To>;
};
// Types are not the same size
namespace ns1 {
struct To { char a; };
struct From { char a; char b; };
static_assert(!bit_cast_is_valid<To, From>);
static_assert(!bit_cast_is_valid<From&, From>);
}
// To is not trivially copyable
namespace ns2 {
struct To { char a; To(To const&); };
struct From { char a; };
static_assert(!bit_cast_is_valid<To, From>);
}
// From is not trivially copyable
namespace ns3 {
struct To { char a; };
struct From { char a; From(From const&); };
static_assert(!bit_cast_is_valid<To, From>);
}
// The return type is ill-formed
namespace ns4 {
struct From { char a; char b; };
static_assert(!bit_cast_is_valid<char[2], From>);
static_assert(!bit_cast_is_valid<int(), From>);
}

View File

@ -0,0 +1,263 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
// <bit>
//
// template<class To, class From>
// constexpr To bit_cast(const From& from) noexcept; // C++20
#include <array>
#include <bit>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <limits>
// std::bit_cast does not preserve padding bits, so if T has padding bits,
// the results might not memcmp cleanly.
template<bool HasUniqueObjectRepresentations = true, typename T>
void test_roundtrip_through_buffer(T from) {
struct Buffer { char buffer[sizeof(T)]; };
Buffer middle = std::bit_cast<Buffer>(from);
T to = std::bit_cast<T>(middle);
Buffer middle2 = std::bit_cast<Buffer>(to);
assert((from == to) == (from == from)); // because NaN
if constexpr (HasUniqueObjectRepresentations) {
assert(std::memcmp(&from, &middle, sizeof(T)) == 0);
assert(std::memcmp(&to, &middle, sizeof(T)) == 0);
assert(std::memcmp(&middle, &middle2, sizeof(T)) == 0);
}
}
template<bool HasUniqueObjectRepresentations = true, typename T>
void test_roundtrip_through_nested_T(T from) {
struct Nested { T x; };
static_assert(sizeof(Nested) == sizeof(T));
Nested middle = std::bit_cast<Nested>(from);
T to = std::bit_cast<T>(middle);
Nested middle2 = std::bit_cast<Nested>(to);
assert((from == to) == (from == from)); // because NaN
if constexpr (HasUniqueObjectRepresentations) {
assert(std::memcmp(&from, &middle, sizeof(T)) == 0);
assert(std::memcmp(&to, &middle, sizeof(T)) == 0);
assert(std::memcmp(&middle, &middle2, sizeof(T)) == 0);
}
}
template <typename Intermediate, bool HasUniqueObjectRepresentations = true, typename T>
void test_roundtrip_through(T from) {
static_assert(sizeof(Intermediate) == sizeof(T));
Intermediate middle = std::bit_cast<Intermediate>(from);
T to = std::bit_cast<T>(middle);
Intermediate middle2 = std::bit_cast<Intermediate>(to);
assert((from == to) == (from == from)); // because NaN
if constexpr (HasUniqueObjectRepresentations) {
assert(std::memcmp(&from, &middle, sizeof(T)) == 0);
assert(std::memcmp(&to, &middle, sizeof(T)) == 0);
assert(std::memcmp(&middle, &middle2, sizeof(T)) == 0);
}
}
template <typename T>
constexpr std::array<T, 10> generate_signed_integral_values() {
return {std::numeric_limits<T>::min(),
std::numeric_limits<T>::min() + 1,
static_cast<T>(-2), static_cast<T>(-1),
static_cast<T>(0), static_cast<T>(1),
static_cast<T>(2), static_cast<T>(3),
std::numeric_limits<T>::max() - 1,
std::numeric_limits<T>::max()};
}
template <typename T>
constexpr std::array<T, 6> generate_unsigned_integral_values() {
return {static_cast<T>(0), static_cast<T>(1),
static_cast<T>(2), static_cast<T>(3),
std::numeric_limits<T>::max() - 1,
std::numeric_limits<T>::max()};
}
bool tests() {
for (bool b : {false, true}) {
test_roundtrip_through_nested_T(b);
test_roundtrip_through_buffer(b);
test_roundtrip_through<char>(b);
}
for (char c : {'\0', 'a', 'b', 'c', 'd'}) {
test_roundtrip_through_nested_T(c);
test_roundtrip_through_buffer(c);
}
// Fundamental signed integer types
for (signed char i : generate_signed_integral_values<signed char>()) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
}
for (short i : generate_signed_integral_values<short>()) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
}
for (int i : generate_signed_integral_values<int>()) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
test_roundtrip_through<float>(i);
}
for (long i : generate_signed_integral_values<long>()) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
}
for (long long i : generate_signed_integral_values<long long>()) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
test_roundtrip_through<double>(i);
}
// Fundamental unsigned integer types
for (unsigned char i : generate_unsigned_integral_values<unsigned char>()) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
}
for (unsigned short i : generate_unsigned_integral_values<unsigned short>()) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
}
for (unsigned int i : generate_unsigned_integral_values<unsigned int>()) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
test_roundtrip_through<float>(i);
}
for (unsigned long i : generate_unsigned_integral_values<unsigned long>()) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
}
for (unsigned long long i : generate_unsigned_integral_values<unsigned long long>()) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
test_roundtrip_through<double>(i);
}
// Fixed width signed integer types
for (std::int32_t i : generate_signed_integral_values<std::int32_t>()) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
test_roundtrip_through<int>(i);
test_roundtrip_through<std::uint32_t>(i);
test_roundtrip_through<float>(i);
}
for (std::int64_t i : generate_signed_integral_values<std::int64_t>()) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
test_roundtrip_through<long long>(i);
test_roundtrip_through<std::uint64_t>(i);
test_roundtrip_through<double>(i);
}
// Fixed width unsigned integer types
for (std::uint32_t i : generate_unsigned_integral_values<std::uint32_t>()) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
test_roundtrip_through<int>(i);
test_roundtrip_through<std::int32_t>(i);
test_roundtrip_through<float>(i);
}
for (std::uint64_t i : generate_unsigned_integral_values<std::uint64_t>()) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
test_roundtrip_through<long long>(i);
test_roundtrip_through<std::int64_t>(i);
test_roundtrip_through<double>(i);
}
// Floating point types
for (float i : {0.0f, 1.0f, -1.0f, 10.0f, -10.0f, 1e10f, 1e-10f, 1e20f, 1e-20f, 2.71828f, 3.14159f,
std::nanf(""),
__builtin_nanf("0x55550001"), // NaN with a payload
std::numeric_limits<float>::signaling_NaN(),
std::numeric_limits<float>::quiet_NaN()}) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
test_roundtrip_through<int>(i);
}
for (double i : {0.0, 1.0, -1.0, 10.0, -10.0, 1e10, 1e-10, 1e100, 1e-100,
2.718281828459045,
3.141592653589793238462643383279502884197169399375105820974944,
std::nan(""),
std::numeric_limits<double>::signaling_NaN(),
std::numeric_limits<double>::quiet_NaN()}) {
test_roundtrip_through_nested_T(i);
test_roundtrip_through_buffer(i);
test_roundtrip_through<long long>(i);
}
for (long double i : {0.0l, 1.0l, -1.0l, 10.0l, -10.0l, 1e10l, 1e-10l, 1e100l, 1e-100l,
2.718281828459045l,
3.141592653589793238462643383279502884197169399375105820974944l,
std::nanl(""),
std::numeric_limits<long double>::signaling_NaN(),
std::numeric_limits<long double>::quiet_NaN()}) {
// Note that x86's `long double` has 80 value bits and 48 padding bits.
test_roundtrip_through_nested_T<false>(i);
test_roundtrip_through_buffer<false>(i);
// On arm64 on Apple platforms, long double is just double, so we don't
// test against int128, but instead against double itself. Otherwise,
// we test against int128 if we have those types available.
#if defined(__aarch64__) && defined(__APPLE__)
# define LONG_DOUBLE_IS_DOUBLE
#endif
#if defined(LONG_DOUBLE_IS_DOUBLE)
test_roundtrip_through<double, false>(i);
#elif !defined(_LIBCPP_HAS_NO_INT128)
test_roundtrip_through<__int128_t, false>(i);
test_roundtrip_through<__uint128_t, false>(i);
#endif
}
return true;
}
// TODO: There doesn't seem to be a way to perform non-trivial correctness
// tests inside constexpr.
constexpr bool basic_constexpr_test() {
struct Nested { char buffer[sizeof(int)]; };
int from = 3;
Nested middle = std::bit_cast<Nested>(from);
int to = std::bit_cast<int>(middle);
assert(from == to);
return true;
}
int main(int, char**) {
tests();
static_assert(basic_constexpr_test());
return 0;
}

View File

@ -156,7 +156,6 @@ feature_test_macros = [ add_version_header(x) for x in [
"name": "__cpp_lib_bit_cast",
"values": { "c++20": 201806 },
"headers": ["bit"],
"unimplemented": True,
}, {
"name": "__cpp_lib_bitops",
"values": { "c++20": 201907 },