Search code examples
c++language-lawyerundefined-behaviortype-punning

downcasting standard layout struct to derived struct with the same data members


I receive a pointer p to a struct S from C code and want to downcast it in C++ with

static_cast<S_extended*>(p) where

  1. S_extended is derived from S but just adds some methods to S so that the struct remains trivial and having standard layout (no virtual functions, no special functions, etc. are added).

  2. No new data members are added into derived class.

Does such casting cause undefined behavior?

For example,

typedef struct S{
  uint32_t data;
} S;

struct S_extended
: public S {
  uint32_t getData(){return data;}
};

S s = {1};

uint8_t f(){
  return static_cast<S_extended*> (&s) -> getData();  // UB ???
}

Solution

  • What you are attempting to do is UB in multiple ways. Let's start with the fact that down-casting to a derived class isn't possible as per C++23 [expr.static.cast] p2 if you don't have an object of the derived type.

    Even if it was possible to static_cast in this case, you would immediately run into another form of undefined behavior. Up until C++20, this would be:

    If a non-static member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined.

    - C++20 [class.mfct.non-static.general] p2

    S is not the same type as S_extended or derived from S, and you are calling a member function of S_extended for an object of type S. The behavior is undefined.

    In C++23, this requirement was generalized for member access E1.E2:

    If E2 is a non-static member and the result of E1 is an object whose type is not similar to the type of E1, the behavior is undefined.

    - C++23 [expr.ref] p8

    Note that similar means S and S_extended would need to be pretty much the same type, and they are not.


    Note: This C++23 change was made in CWG2535 Type punning in class member access