3

C++20 introduced std::bit_cast for treating the same bits as if they were a different type. So, basically it does this:

template <typename T1, typename T2>
T2 undefined_bit_cast(T1 v1) {
    T1 *p1 = &v1;
    T2 *p2 = (T2 *)p1; // Uh oh.
    T2 v2 = *p2; // Oh no! Don't do that!
    return v2;
}

except without the undefined behavior that allows a compiler to replace this method with a hard drive deletion method.

I would like to use std::bit_cast, but I'm stuck using C++11. How do I correctly implement my own custom_bit_cast, using no undefined behavior, without using the actual std::bit_cast?

Followup question: is there a pointer bit cast? A safe way to have a T1 * and a T2 * pointing at the same location, and be able to read and write from both, without it being undefined behavior?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Craig Gidney
  • 17,763
  • 5
  • 68
  • 136
  • Your follow-up question is [answered by an answer](https://stackoverflow.com/a/67766242/427158) to a related question of mine. – maxschlepzig May 31 '21 at 16:59
  • 3
    FWIW, the [cppreference entry on `std::bitcast()`](https://en.cppreference.com/w/cpp/numeric/bit_cast#Notes) contains an example implementation. – maxschlepzig May 31 '21 at 19:10

2 Answers2

5
template <class T2, class T1>
T2 cpp11_bit_cast(T1 t1) {
  static_assert(sizeof(T1)==sizeof(T2), "Types must match sizes");
  static_assert(std::is_pod<T1>::value, "Requires POD input");
  static_assert(std::is_pod<T2>::value, "Requires POD output");

  T2 t2;
  std::memcpy( std::addressof(t2), std::addressof(t1), sizeof(T1) );
  return t2;
}

you can probably relax the pod restriction to trivially copyable.

Compilers are pretty good with the above being optimized to good assembly.

As for the pointer bit, there is no safe way to read an object of type T1 as if it was an object of type T2 where T1 and T2 are arbitrary. There are cases where it is allowed, but they are quite narrow.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 2
    The ability of compilers to optimize `memcpy` is platform-dependent. On something like a 32-bit ARM platform, a compiler which is configured for `-fno-strict-aliasing` or equivalent may be able to perform `*(T1*)p1 = *(T2*)p2;` with types that hold four words each using a load-multiple instruction followed by a store-multiple instruction, but using `memcpy` would make it necessary to either use 16 loads and 16 stores, or else call a function that checks object alignment and uses 32-bit loads/stores when possible, but uses byte loads/stores when needed. – supercat May 16 '21 at 17:40
  • @supercat Arm compiler [has no problem](https://godbolt.org/z/PPes7nP6Y) with it. The compiler is well aware of T's alignment – Yakk - Adam Nevraumont Jul 28 '22 at 16:56
0

Almost every C or C++ compiler, likely including every compiler that would be suitable for tasks that would involve bit_cast, includes configuration options to extend the languages by defining behaviors upon which the Standard imposes no requirements. I haven't seen the any published Rationale documents for the C++ Standard, but the authors of the C Standard explicitly describe UB as, among other things, identifying areas of "conforming language extension", and regard support for such "popular extensions" as a quality-of-implementation issue outside the Standard's jurisdiction.

The fact that a construct would not be portable among all C or C++ compiler configurations does not imply that it cannot or should not be safely used on configurations that support it. I'd suggest designing some headers that can define macros to handle things one of three ways, depending upon implementation:

  1. For implementations that support bit_cast, use that.

  2. For implementations that extend the language with compiler-specific syntax to allow support for type punning, use that.

  3. For other implementations, specify that compilers must be configured to extend the language with support for type punning at least when using common sequences of operations.

While there may exist implementations for which none of those approaches would be suitable, on most implementations at least one of them would be effective.

supercat
  • 77,689
  • 9
  • 166
  • 211