Why Doesn't Reinterpret_Cast Force Copy_N for Casts Between Same-Sized Types

Why Doesn't reinterpret_cast Force copy_n for Casts between Same-Sized Types?

why doesn't reinterpret_cast handle that for me?

One reason is that the size, alignment, and bit representations aren't specified, so such a conversion wouldn't be portable. However, that wouldn't really justify making the behaviour undefined, just implementation-defined.

By making it undefined, the compiler is allowed to assume that expressions of unrelated types don't access the same object, which can allow better optimisation. For example, in the following:

int   & i = something();
float & f = something_else();

const int i1 = i;
f = 42;
const int i2 = i;

the compiler can assume that i1 and i2 both have the same value (i being unchanged by the assignment to f), and optimise them into a single constant. Breaking the assumption will then cause undefined behaviour.

Or is there something else available so I don't have to jump through this hoop?

Copying the bytes is the only well-defined way to reinterpret one object type as an unrelated type.

Aliasing with reinterpret_cast or a union might work sometimes (assuming the size etc. match), but might trip you up if the optimiser gets too clever with undefined behaviour.

Why doesn't this reinterpret_cast compile?

Perhaps a better way of thinking of reinterpret_cast is the rouge operator that can "convert" pointers to apples as pointers to submarines.

By assigning y to the value returned by the cast you're not really casting the value x, you're converting it. That is, y doesn't point to x and pretend that it points to a float. Conversion constructs a new value of type float and assigns it the value from x. There are several ways to do this conversion in C++, among them:

int main()
{
int x = 42;
float f = static_cast<float>(x);
float f2 = (float)x;
float f3 = float(x);
float f4 = x;
return 0;
}

The only real difference being the last one (an implicit conversion) will generate a compiler diagnostic on higher warning levels. But they all do functionally the same thing -- and in many case actually the same thing, as in the same machine code.

Now if you really do want to pretend that x is a float, then you really do want to cast x, by doing this:

#include <iostream>
using namespace std;

int main()
{
int x = 42;
float* pf = reinterpret_cast<float*>(&x);
(*pf)++;
cout << *pf;
return 0;
}

You can see how dangerous this is. In fact, the output when I run this on my machine is 1, which is decidedly not 42+1.

Safety of reinterpret_cast on pointer to template aggregate type

there seems to be a statement for legal use that covers what I'm trying to do ...

That's not what that exception says or means. That exception says that given

struct S { int i; } s;

you can use *reinterpret_cast<int *>(&s) to access s.i.

There is no similar exception for what you're trying to do. What you're trying to do is simply not valid in C++. Even the below is invalid:

struct S { int i; };
struct T { int i; };
int f(S s) { return ((T &) s).i; }

and compilers optimise based on the assumption that you don't write code like that.

For an actual example that fails at run-time with a current compiler:

#include <cstdlib>
struct S { int i; };
struct T { int i; };
void f(S *s, T *t) { int i = s->i; t->i++; if (s->i == i) std::abort(); }

Here, GCC optimises away the check s->i == i (GCC 4.9.2, with -O2 in the command-line options), and unconditionally calls std::abort(), because the compiler knows that s and t cannot possibly point to the same region of memory. Even though you might try to call it as

int main() { S s = { 0 }; f(&s, reinterpret_cast<T *>(&s)); }

Out of bounds array accesses in C++ and reinterpret_cast

So the reinterpret_cast is undefined behavior, we can reinterpret_cast to a char or unsigned char we can never cast from a char or unsigned char, if we do:

Accessing the object through the new pointer or reference invokes undefined behavior. This is known as the strict aliasing rule.

So yes this is a violation of the strict aliasing rule.

Why is it allowed to reinterpret_cast integral, enumeration and pointer-to-member types to themselves?

It was part of resolving DR 799. The issue was as follows:

The note in 8.2.10 [expr.reinterpret.cast] paragraph 2 says,

Subject to the restrictions in this section, an expression may be cast
to its own type using a reinterpret_cast operator.


However, there is nothing in the normative text that permits this conversion, and
paragraph 1 forbids any conversion not explicitly permitted.

The idea in the note was deemed worthwhile, that reinterpret_cast should be allowed to do the identity conversion. So the normative text you ask about was added. I can assume the restriction to some fundamental types is a cautious first (and maybe even only) step. Since it doesn't open the can of worms associated with class types and the need to call their constructors. reinterpret_cast is all about not creating new objects, and one can do that with fundamental types. Not sure the same applies to class types.

Is this type punning well-defined?

You cannot do this: &reinterpret_cast<PunnerToUInt32*>(&x)

The rules on reinterpret_cast state:

When a pointer or reference to object whose dynamic type is DynamicType is reinterpret_cast (or C-style cast) to a pointer or reference to object of a different type AliasedType, the cast always succeeds, but the resulting pointer or reference may only be used to access the object if one of the following is true:

  • AliasedType is (possibly cv-qualified) DynamicType
  • AliasedType and DynamicType are both (possibly multi-level, possibly cv-qualified at each level) pointers to the same type T
  • AliasedType is the (possibly cv-qualified) signed or unsigned variant of DynamicType
  • AliasedType is an aggregate type or a union type which holds one of the aforementioned types as an element or non-static member (including, recursively, elements of subaggregates and non-static data members of the contained unions): this makes it safe to obtain a usable pointer to a struct or union given a pointer to its non-static member or element.
  • AliasedType is a (possibly cv-qualified) base class of DynamicType
  • AliasedType is char or unsigned char: this permits examination of the object representation of any object as an array of unsigned char

Because none of these are true for the combination of DynamicType being float and AliasedType being PunnerToUInt32 the pointer may not be used to access the object, which you are doing. Making the behavior undefined.

For more information see: Why Doesn't reinterpret_cast Force copy_n for Casts between Same-Sized Types?

EDIT:

Breaking down the 4th bullet int bite size chunks yields:

  1. "AliasedType"
    Here taken to be PunnerToUInt32
  2. "is an aggregate type or a union type"
    PunnerToUInt32 qualifies since it meets the qualifications of an aggregate type:

    • array type
    • class type (typically, struct or union), that has

      • no private or protected non-static data members
      • no user-provided constructors, including those inherited from public bases (explicitly defaulted or deleted constructors are allowed)
      • no virtual, private, or protected base classes
      • no virtual member functions
  3. "which holds one of the aforementioned types as an element or non-static member (including, recursively, elements of subaggregates and non-static data members of the contained unions)"
    Again PunnerToUInt32 qualifies because of it's float fl member

  4. "this makes it safe to obtain a usable pointer to a struct or union"
    This is the final correct part as AliassedType is a PunnerToUInt32
  5. "given a pointer to its non-static member or element"
    This is a violation, because the DynamicType which is x is not a member of PunnerToUInt32

Because of the violation of part 5 operating on this pointer is undefined behavior.

If you care for some recommended reading you can check out Empty Base Optimization if not I'll give you the primary relevance here:

Empty base optimization is required for StandardLayoutTypes in order to maintain the requirement that the pointer to a standard-layout object, converted using reinterpret_cast, points to its initial member

Thus you could exploit reinterpret_cast's 4th bullet by doing this:

PunnerToUInt32 x = {13, 42.0F};
auto y = reinterpret_cast<PunnerToUInt32*>(&x.ui32);

Live Example

Changing type without changing bits

If you don't want to have undefined behavior due to violating the aliasing restrictions (C++11 3.10/10) then you need to access the object representations as characters:

template <typename T>
int_type<T> caster(const T& value) {
int_type<T> v;
static_assert(sizeof(value) == sizeof(v), "");
std::copy_n(reinterpret_cast<const char*>(&value),
sizeof(T),
reinterpret_cast<char*>(&v));
return v;
}

High quality compilers will optimize the copy away. E.g., this program:

int main() {
return caster(3.14f);
}

effectively optimizes to return 1078523331; on Intel processors.



Related Topics



Leave a reply



Submit