1

For this question, no polymorphism shall be involved, i.e. no virtual methods, no virtual base classes. Just in case it matters, my case does not involve any of those.

Assume I have a class Derived which has an unambiguous accessible parent of type Base, with no polymorphism (no virtual methods, no virtual base classes), but possibly involving indirect and/or multiple inheritance. Assume further I have a valid pointer Derived *derived (points to an object of type Derived or a subclass thereof).

In this case, I believe static_cast<Base*>(derived) is valid (results in a valid usable pointer). When the ancestry chain between Base and Derived involves multiple inheritance, this static_cast might imply pointer adjustments to locate the Base instance within the Derived instance. To do that, the compiler needs to know the inheritance chain, which he does in this case. However, if an intermediate cast to void * is inserted, that inheritance chain information is hidden from the compiler. For which inheritance chain is such a static cast valid nonetheless? I expect one of the following:

  • None at all? Accessing a static_cast from void pointer is undefined behaviour unless the pointer really points to the exact type.
  • For all chains without multiple-inheritance? Then, the compiler could guarantee that Base is always at the start of Derived - but what says the standard?
  • For all chains where Base is found within the first parent class of all intermediate multiple inheritance chains? Maybe the start of Base and Derived still matches?
  • Always? static_cast to void pointer could always adjust to the start of the very first parent, and static_cast from void pointer undo that adjustment. But with multiple inheritance, the "very first parent" is not necessarily a parent of all parents.
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
burnpanck
  • 1,955
  • 1
  • 12
  • 36
  • The usual way to solve this is the CRTP (aka static polymorphism). It's safe to use the `static_cast` then. – πάντα ῥεῖ Aug 24 '19 at 10:12
  • `static_cast(derived)` will be valid even if exist virtual methods. but `static_cast((void*)derived)` already wrong and give formally correct pointer only if address of Base same as address of Derived. (say even if Derived inherit only from Base, but Base have no virtual functions, and Derived have it - address will be different). in any case - for what try use incorrect `static_cast((void*)derived)` instead correct `static_cast(derived)` ? – RbMm Aug 24 '19 at 10:20
  • @RbMm: Such casts might be required when the intermediate `void*` is stored somewhere outside of my control. In this case it is a `std::experimental::coroutine_handle<>`. I was wondering if I could implement allocation/deallocation in a sub-class, pass that to the compiler-generated coroutine machinery, and later interact only with a base class. For efficiency reasons, I would prefer to avoid runtime polymorphism. – burnpanck Aug 24 '19 at 17:53
  • *intermediate `void*`* - for what object ? if you know this at compile time - you need first cast `void*` to this type – RbMm Aug 24 '19 at 17:58
  • It's the `promise` used within C++2a coroutines. In clang + libc++, the actual path of the pointer is roughly `coroutine_handle = __builtin_coro_promise(std::addressof(promise),...,true)` and then later `promise = *static_cast(__builtin_coro_promise(coroutine_handle,...,false))`. Those two calls are essentially compiler-generated during coroutine transformation. – burnpanck Aug 24 '19 at 18:05
  • The compiler has magic at hand so it knows the type of the Promise where it must. But most of my code doesn't need to know the exact type, because it is shared between a number of variants within a base-class – burnpanck Aug 24 '19 at 18:10

1 Answers1

5

static_cast<Base*>(static_cast<void*>(derived)) has a name in the C++ standard. It's called a reinterpret_cast. It's specified in [expr.reinterpret.cast] paragraph 7:

An object pointer can be explicitly converted to an object pointer of a different type. 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)). [ Note: 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. — end note ]

A reinterpret_cast is us telling the compiler to treat the pointer as something else. There is no adjustment that the compiler can or will do under this instruction. If we lied, than the behavior is simply undefined. Does the standard say when such a reinterpret_cast is valid? It does actually. There is a concept of pointer interconvertiblity defined at [basic.compound] paragraph 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 third bullet is your answer. The objects in the class hierarchy must uphold restrictions (be standard layout from top base to most derived), and only then is the cast guaranteed to give well defined results.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • That's unless you take the std seriously. It says explicitly that ptrs are trivial types so all that pointer-interconvertible stuff falls appart! – curiousguy Aug 24 '19 at 12:02
  • @StoryTeller: Well-written answer, thank you! Now I'm off trying to determine if my classes are standard-layout... – burnpanck Aug 24 '19 at 18:00
  • @curiousguy: I do not fully understand your comment. Why would it matter that ptrs are trivial types? Doesn't it just imply that you may safely *cast* pointers, nothing more? I, on the other hand, would like to know if I may safely *use* those casted pointers. – burnpanck Aug 24 '19 at 18:12
  • Pls see my many Q about poking at the bits of ptr [Is memcpy of a pointer the same as assignment?](https://stackoverflow.com/q/32048698/963864) [Are pointer variables just integers with some operators or are they “symbolic”?](https://stackoverflow.com/q/32045888/963864) [Dereferencing a 50% out of bound pointer (array of array)](https://stackoverflow.com/q/32100245/963864). Yes the code in that last one seems demented by there is a point: if ptr are trivial then all that demented stuff is legit. A type is trivial => two identical bit patterns are semantically indistinguishable. – curiousguy Aug 24 '19 at 22:08
  • Well, the point is the identical bit-pattern. All those linked questions poke around bits of pointers to the same type. But for pointers to different types, static_cast is not generally the same as memcpy! This is because upcasting and downcasting requires adjustment of the value (i.e. the bits) of the pointer. That's what my question is about: Does the standard declare rules, when the compiler has to lay-out types in such a way that no adjustment is made. StoryTeller answered very precisely; there are rules, but they are narrower than what I would have expected. – burnpanck Aug 25 '19 at 10:18
  • @burnpanck "_But for pointers to different types, static_cast is not generally the same as memcpy!_" Exactly. But then even when it's the same, the guarantees available from the correct cast isn't available for the wrong cast or from no cast at all (reinterpretation of the pointer bits). (That's assuming you dismiss the part of the std the std committee wants you to dismiss, the part about trivial types. The std is a mess.) – curiousguy Aug 31 '19 at 18:48