How to Legally Reinterpret_Cast Between Layout-Compatible Standard-Layout Types

Can I legally reinterpret_cast between layout-compatible standard-layout types?

but I can't see anything in the standard that therefore allows me to reinterpret_cast between them, even though that seems like the reasonable interpretation of "value representation". Is this technically allowed by the standard?

No. The standard is clear (see [basic.lval] p10) about which types can be aliased, and layout-compatible types are not included.

If not, what does knowing the value representation of a type give you?

If the types are both trivially copyable and have the same value representation then you could memcpy from an object of one type to an object of the other type, and vice versa. If they're not trivially copyable then it doesn't give you much at all.

AFAICT the standard doesn't actually say what can and can't be done with layout-compatible types.

What's the purpose of layout-compatible types?

The standard does define one specific case where layout compatibility matters: in unions. If two members are layout-compatible, and one of them is the active union member, then you may access that object through pointers/references to any layout-compatible member of that union. This is a consequence of the "common initial sequence" rule.

reinterpret_cast for almost pod data (is layout-compatibility enough)

I believe the following are valid because complex_base is POD

You are wrong. d[0] does not refer to the first member of a complex_base object. Its alignment may therefor not be good enough for a complex_base object, therefor such a cast is not safe (and not allowed by the text you quote).

Does the following complete a valid cast to complex even though complex is NOT POD?

cb1 and cb2 do not point to subobjects of an object of type complex, therefor the static_cast produces undefined behavior. Refer to 5.2.9p5 of C++03

If the lvalue of type "cv1 B" is actually a sub-object of an object of type D, the lvalue refers to the enclosing object of type D. Otherwise, the result of the cast is undefined.

It's not enough if merely the types involved fit together. The text talks about a pointer pointing to a POD-struct object and about an lvalue referring to a certain subobject.
oth complex and complex_base are standard-layout objects. The C++0x spec says, instead of the text you quoted:

Is POD-ness requirement too strict?

This is a different question, not regarding your example code. Yes, requiring POD-ness is too strict. In C++0x this was recognized, and a new requirement which is more loose, "standard-layout" is given. I do think that both complex and complex_base are standard-layout classes, by the C++0x definition. The C++0x spec says, instead of the text you quoted:

A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa.

I interpret that as allowing to cast a pointer to a double, which actually points to a complex member (member by inheritance), to be casted to a complex*. A Standard-layout class is one that either has no base classes containing non-static data, or has only one base-class containing non-static data. Thus there is an unique "initial member".

Are enumeration types layout compatible with their underlying type?

NO, there is no black-letter quote from the Standard that specifies this. The closest that one can get is point 7 of that same paragraph

7 [...] the underlying type is an integral type that can represent all
the enumerator values defined in the enumeration. If no integral type
can represent all the enumerator values, the enumeration is
ill-formed. [...]

Furthermore, 4.5 Integral promotions [conv.prom] says

4 A prvalue of an unscoped enumeration type whose underlying type is
fixed (7.2) can be converted to a prvalue of its underlying type.

As pointed out in the comments, there could be (devious IMO) implementations that have different endianess between an enum and its underlying type. That would be a Quality of Implementation issue. For all practical purposes, layout-compatibility should be expected.

reinterpret_cast vs. static_cast for writing bytes in standard-layout types?

In this case (converting object pointers), reinterpret_cast is identical to the two nested static_cast via void*

5.2.10 Reinterpret cast [expr.reinterpret.cast]

7 An object pointer can be explicitly converted to an object pointer
of a different type.72 When a prvalue v of object pointer type is
converted to the object pointer type “pointer to cv T”, the result is
static_cast<cv T*>(static_cast<cv void*>(v)). Converting a prvalue of
type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are
object types and where the alignment requirements of T2 are no
stricter than those of T1) and back to its original type yields the
original pointer value.

It is better to use reinterpret_cast to signal your intent here.

UPDATE: as mentioned in the comments, this was apparently added in C++11, although most C++98 compilers already supported it (see also this Q&A)

Can one access private member functions through casting to layout-compatible types?

Yes, you could create a type that uses the same layout as the type you're trying to pilfer from, then reinterpret_cast from that type to your layout compatible type. But this is only protected by the standard if both the source and destination types are standard layout types (and of course, it only actually works if their layouts are the same). So if the source has virtual functions, you're screwed.

This seems to satisfy both of Sutter's issues here. The rules of standard layout ensure that two types that are both standard layout that define the same members in the same order are layout-compatible (section 9.2, paragraph 17):

Two standard-layout struct (Clause 9) types are layout-compatible if they have the same number of non-static data members and corresponding non-static data members (in declaration order) have layout-compatible types (3.9).

And the rules for reinterpret_cast specify the meaning of the conversion between two standard layout types (section 5.2.10, paragraph 7):

An object pointer can be explicitly converted to an object pointer of a different type.
When a prvalue v of type “pointer to T1” is converted to the type “pointer to cv T2”, the result is static_cast<cv T2*>(static_cast<cv void*>(v)) if both T1 and T2 are standard-layout types (3.9) and the alignment requirements of T2 are no stricter than those of T1, or if either type is void.

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.

Accessing private data with reinterpret_cast

No, the behavior is undefined. For such a reintepret_cast to have meaning, the two objects must be interconvertible

[basic.compound]

4 Two objects a and b are pointer-interconvertible if:

  • they are the same object, or
  • one is a union object and the other is a non-static data member of that object ([class.union]), or
  • one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no
    non-static data members, any base class subobject of that object
    ([class.mem]), or
  • there exists an object c such that a and c are pointer-interconvertible, and c and b are pointer-interconvertible.

If two objects are pointer-interconvertible, then they have the same
address, and it is possible to obtain a pointer to one from a pointer
to the other via a reinterpret_­cast. [ Note: An array object and its
first element are not pointer-interconvertible, even though they have
the same address. — end note ]

The only bullet that might apply is the one about standard layout classes. If we consult that definition, we see

[class.prop]

3 A class S is a standard-layout class if it:

  • has no non-static data members of type non-standard-layout class (or array of such types) or reference,
  • [...]

there is an immediate problem. Any non-static data members of the object must be standard layout themselves. There is no guarantee std::string is a standard layout type. So the behavior is undefined.



Related Topics



Leave a reply



Submit