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

84 views Asked by At

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 ???
}
3

There are 3 answers

0
Jan Schultke On BEST ANSWER

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

1
Silvio Mayolo On

The fact that it's standard layout doesn't come into play at all. A static_cast is an absolute assertion by the programmer to the compiler that this value has the correct type. If the assertion is false, it's undefined behavior. Will it work? Probably, for a minute. But it's certainly not standard behavior.

I don't know what your background is, but I've seen a lot of Python and Java developers who start in C++ and try to fit everything into instance methods to get that nice, juicy foo.bar() syntax. And that's really just not how C++ is designed to work.

In C++, it's recommended to use free functions unless the function you're writing absolutely needs to access private members or should be virtual. In your case, since you're just adding helper functions, it's likely you can just write them as free-standing functions which take an S* (or, better, S& or const S&) as their first argument. You do have to call them as free functions (since Uniform Function Call Syntax has been proposed and declined many times by now), but you get nice loose coupling, argument-dependent lookup, and implicit conversions.

0
user17732522 On

Does such casting cause undefined behavior?

Yes, the cast itself has undefined behavior. Whether or not you are adding data members and whether or not the classes are standard-layout or trivial is irrelevant. See [expr.static.cast]/11.