2

Given this sample:

  class Base
  {
      public:
      void foo() {}; 
  };

  class Derived : public Base
  {     
  };

  int main()
  {
      Base b;
      Derived* d = static_cast<Derived*>(&b);
      d->foo();
  }

I just have three cases: when void foo():

  • is member of Base,
  • and when it is member of Derived,
  • and when it is member of Base and Derived.

My questions are:

  • Does the member access expression d->foo() is undefined behavior in all three cases?.

  • If the member access expression is UB, Does the only workaround is to use dynamic_cast?

  • https://stackoverflow.com/questions/47310700/c-static-cast-downcast-validity – pm100 Mar 16 '22 at 23:29
  • note that `foo` is a member of both `Base` and `Derived` in your example. Derived classes inherit their parents' members. – M.M Mar 17 '22 at 00:36

1 Answers1

4

From the C++ standard §7.6.1.9.11

A prvalue of type “pointer to cv1 B”, where B is a class type, can be converted to a prvalue of type “pointer to cv2 D”, where D is a complete class derived from B,

...

If the prvalue of type “pointer to cv1 B” points to a B that is actually a base class subobject of an object of type D, the resulting pointer points to the enclosing object of type D. Otherwise, the behavior is undefined.

So using static_cast to downcast is only valid if you know (via some other means) that the cast is valid. If you take a Base that isn't a Derived and use a static_cast to pretend that it is, you've invoked undefined behavior, even before you try to dereference the pointer. So you could remove the d->foo() line and still have undefined behavior. It's the cast that's bad.

If your goal is to conditionally check whether you've got an instance of a subclass, then you want dynamic_cast.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
  • 1
    *"So you could remove the d->foo() line and still have undefined behavior"*. does that's mean this expression `static_cast(&b);` yields UB? –  Mar 16 '22 at 23:34
  • 1
    Yes. The `static_cast` line yields undefined behavior. You're casting a value to a type that it doesn't belong to, and you're using a cast that effectively asserts it's safe without checking. – Silvio Mayolo Mar 16 '22 at 23:35
  • .. and then dereferencing the pointer `d` yields UB, regardless of what the member it tries to access? –  Mar 16 '22 at 23:37
  • 1
    Worse than that. [Undefined behavior](https://en.cppreference.com/w/cpp/language/ub) propagates forward and backward in your code, so as long as that line *exists* in your program, every function you've written is UB. A clever C++ compiler could replace your entire program with `cout << "Hello world"` and still be conformant to the standard. Basically, compilers are allowed to assume UB doesn't happen and optimize around that use case, so if UB happens, then [ex falso quodlibet](https://en.wikipedia.org/wiki/Principle_of_explosion) anything is possible. – Silvio Mayolo Mar 16 '22 at 23:40
  • 2
    @SilvioMayolo UB propagates only if the code path is executed. `if (false) { UB; }` does not cause any bad things to happen. On the other hand, `a(); if (flag) UB;` can legally be converted to `if (flag) { UB; } else a();` because the `UB` propagates backward to prevent `a()` from executing at all if `flag` is true. – Raymond Chen Mar 17 '22 at 01:04