Safe equivalent of std::bit_cast in C++11

3k Views Asked by At

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?

2

There are 2 best solutions below

4
Yakk - Adam Nevraumont On BEST ANSWER
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.

0
supercat On

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.