There are a few questions and answers on the site already concerning pointer-interconvertibility of structs and their first member variable, as well as structs and their first public base. This question is one of them, for example.
However, what I'm interested in is not the fact that it's undefined behavior to reinterpret_cast
(or static_cast
through a void *
) between a non-standard-layout struct and its public base, but rather the reasoning why the C++ standard currently forbids such casts. The existing questions and answers don't cover this aspect.
Consider the following example in particular (Godbolt):
#include <type_traits>
struct Base {
int m_base_var = 1;
};
struct Derived: public Base {
int m_derived_var = 2;
};
Derived g_derived;
constexpr Derived *g_pDerived = &g_derived;
constexpr Base *g_pBase = &g_derived;
constexpr void *g_pvDerived = &g_derived;
//These assertions all hold
static_assert(!std::is_pointer_interconvertible_base_of_v<Base, Derived>);
static_assert((void *)g_pDerived == (void *)g_pBase);
static_assert((void *)g_pDerived == g_pvDerived);
static_assert((void *)g_pBase == g_pvDerived);
//This is well-defined and returns &g_derived
Derived * getDerived() {
return static_cast<Derived *>(g_pvDerived);
}
//This is also well-defined; outer static_cast added to illustrate the sequence of conversions
Base * getBase() {
return static_cast<Base *>(static_cast<Derived *>(g_pvDerived));
}
//This is UB due to the first static_assert!
Base * getBaseUB() {
return static_cast<Base *>(g_pvDerived);
}
As you can see from the Godbolt link, all three functions compile to the exact same assembly on x86-64 GCC. However, the standard forbids the third variant since Base
is not a pointer-interconvertible base of Derived
.
My question is: Is there an obvious reason why the standard forbids this kind of cast? In particular, on all implementations that I know of, the value of a pointer to the Base
subobject is the same as that of the pointer to the whole Derived
, and I don't see a particular reason why Derived
should not be considered standard-layout anymore. (In other words, Base
lives at offset zero within Derived
.) Would it be legal for a C++ implementation to place Base
at a non-zero offset within Derived
? (Is there maybe an implementation already that does this?)
Note that this question is only about cases without virtual member functions / virtual inheritance / multiple inheritance.
virtual
anything, the type wouldn't be standard layout, as that is explicitly forbidden already. You don't need to double-forbid it. – Decisive