Search code examples
c++pointersreinterpret-cast

Does the following reinterpret_cast lead to undefined behavior?


Does the reinterpret_cast in the code below lead to undefined behavior? In case it does, is it possible to define rpd in a type-safe manner?

class Base
{ 
public:
  virtual ~Base() = default;
};

class Derived : public Base { };

int main(void)
{
  Derived d;
  Base* pb = &d;
  Base*& rpb = pb;

  Derived*& rpd = reinterpret_cast<Derived*&>(rpb);

  return 0;
}

Sort of related to my previous recent question. Context behind this; I am experimenting with an adapter class that should allow vectors containing covariant pointer types to be used as covariant types themselves.


Solution

  • The cast itself doesn't have UB (see [expr.reinterpret.cast]), but accessing the referred pointer (rpb) through the reinterpreted reference (rpd) does:

    [basic.lval] (standard draft)

    If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:56

    56) The intent of this list is to specify those circumstances in which an object may or may not be aliased.

    • (8.1) the dynamic type of the object,

    Does not apply, the dynamic type is the static type which is Base*, not Derived* that is the type of the glvalue.

    • (8.2) a cv-qualified version of the dynamic type of the object,

    There are no cv-qualifications, and the types still don't match.

    • (8.3) a type similar to the dynamic type of the object,

    Does not apply. This is about cv-qualifier de-compositions, see [conv.qual] (sorry, those many subscripts in the paragraphs are a pain to type in html and are necessary to keep the text readable).

    • (8.4) a type that is the signed or unsigned type corresponding to the dynamic type of the object,

    Only relevant to integral types.

    • (8.5) a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,

    Ditto.

    • (8.6) an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),

    Derived* is neither an aggregate nor a union.

    • (8.7) a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,

    Derived* is not a base of Base*.

    • (8.8) a char, unsigned char, or std​::​byte type.

    Derived* is none of those.


    As none of the exceptions apply, the behaviour of accessing a Base* through a glvalue of Derived* type is undefined.


    I am experimenting with an adapter class that should allow vectors containing covariant pointer types to be used as covariant types themselves.

    Your experiment would fail to uphold basic object oriented principles.

    Base references are covariant with derived, because you cannot do anything with a derived object through a base reference that you couldn't do with the derived object itself.

    Containers of base type cannot be covariant with containers of derived, because you can do something with a container of (derived through a "referencing" container of) base that you couldn't do with a container of derived: Add objects of other derived types.

    Although, if the containers are immutable... it might work conceptually. Actually implementing it in C++ is another matter.