14

Consider this example, where the base class has some data members, while derived one only provides an additional method:

struct TestBase
{
    int x;
    TestBase() : x(5) {}
};

struct TestDerived : public TestBase
{
    void myMethod()
    {
        x=8;
    }
};

int main()
{
    TestBase b;
    TestDerived& d=static_cast<TestDerived&>(b);
    d.myMethod();
}

This is downcasting to wrong type, so AFAIU it has undefined behavior. But are there maybe some exceptions to such cases as this, where the derived class' layout is identical to that of base class?

Ruslan
  • 18,162
  • 8
  • 67
  • 136
  • `TestDerived`/`TestBase` is a standard layout type, so that should give pretty good guarantees that this will work. Doesn't answer the question of whether it's legal, though. – MicroVirus Nov 08 '15 at 20:10
  • Maybe this answers the question: [Safety of casting between pointers of two identical classes?](http://stackoverflow.com/questions/7762929/safety-of-casting-between-pointers-of-two-identical-classes) – MicroVirus Nov 08 '15 at 20:24
  • Or maybe here: [reinterpret_cast for almost pod data (is layout-compatibility enough)](http://stackoverflow.com/questions/5060879/reinterpret-cast-for-almost-pod-data-is-layout-compatibility-enough) – MicroVirus Nov 08 '15 at 20:28
  • Somehow related: [Set derived class field by converting base class pointer](http://stackoverflow.com/q/33077242/2020827) – sergej Nov 08 '15 at 21:24
  • Also see: [Are there cases where downcasting an actual Base to a Derived would be defined?](https://stackoverflow.com/q/20263888/). This is really a duplicate, but I hesitate to mark it as a duplicate because I think the answers on this question are clearer. – jamesdlin Feb 10 '23 at 08:15

2 Answers2

8

From the standard (emphasis mine):

§5.2.9 Static cast [expr.static.cast] ...

(2) An lvalue of type “cv1 B,” where B is a class type, can be cast to type “reference to cv2 D,” where D is a class derived from B, if a valid standard conversion from “pointer to D” to “pointer to B” exists, cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is neither a virtual base class of D nor a base class of a virtual base class of D. The result has type “cv2 D.” An xvalue of type “cv1 B”may be cast to type “rvalue reference to cv2 D” with the same constraints as for an lvalue of type “cv1 B.” If the object of type “cv1 B” is actually a subobject of an object of type D, the result refers to the enclosing object of type D. Otherwise, the behavior is undefined.

My first guess was that the cast should be valid in this case, because I got confused by the term subobject.

Now (thanks to @T.C. and @M.M), it is obvious that the behavior is undefined in this case.

The cast would be valid in the following example:

int main()
{
    TestDerived d;
    TestBase &br = d; // reference to a subobject of d
    TestDerived &dr = static_cast<TestDerived&>(br);  // reference to the original d object
    d.myMethod();
}

Here, an object of class TestDerived (d) will have a subobject of class TestBase (br is a reference to this object).

sergej
  • 17,147
  • 6
  • 52
  • 89
-1

static_cast<> will generate a compiler error if you attempt to cast between incompatible types, but it makes no assurances at compile time or runtime that the cast is valid.

Since TestDerived inherits from TestBase, it's a legal cast as permitted by the static_cast operator, but not all downcasts are necessarily a safe casts.

In the above code, it just happens to work safely - most likely because TestMethod only accesses base class members, is single inheritance, no vtables, and not doing anything complex. As such, the compiler is likely treating the cast as a no-op simple example. Others will tell you "this is undefined behavior" - and not to assume anything about code written like this. (And they would be correct too.)

selbie
  • 100,020
  • 15
  • 103
  • 173
  • 2
    Well here `dynamic_cast` won't work because `TestBase` isn't polymorphic. – Ruslan Nov 08 '15 at 18:18
  • 2
    This is incorrect, the cast itself causes undefined behaviour (see the bolded text in sergej's answer) – M.M Nov 08 '15 at 20:14
  • I believe this answer is good description of what most implementations will *actually* do, but misses the point of the question. *Predictable* != *Defined*. – ams Nov 10 '15 at 11:11
  • @ams - That's good feedback. I try to give answers on SO that are practical from the perspective of how compilers and linkers work in practice on modern computers. It leads to a deeper understanding of how C/C++ is actually implemented - which in turn can make someone better at debugging and analyzing code. But there are **a lot** of *language lawyers* in the C/C++ tags who downvote any such discussion if it flies in the fact of "what the standard says". I have to tread carefully when giving practical answers when this group is around. – selbie Nov 10 '15 at 19:11
  • 1
    That's fine, but you're going to need to say so explicitly and understand yourself that you're making a bet that nobody will ever change anything that breaks your assumption (both in your code and the compiler). – ams Nov 10 '15 at 22:02
  • Also, I'm not convinced you haven't broken aliasing rules, and *that's* a bug that's going to bite one day. Of course, this sort of thing was standard practice in C (and equally dodgy) and that's why C++ was invented. ...,. OK, maybe not the *only* reason. – ams Nov 10 '15 at 22:08
  • I've amended my answer to indicate what's *likely* happening with a nod towards "undefined behavior". – selbie Nov 10 '15 at 22:17
  • The cast is "legal" in the same sense that `char *p; p[100] = 'x';` is legal, or `1 / 0`. (i.e. a "not" sense) . Perhaps it'd be better to say *syntactically correct* or something. – M.M Nov 10 '15 at 22:41
  • This answer gives a good explanation for why the code might appear to work - this time. But without a deeper warning about undefined behavior, you might find that with the next compiler version it does something altogether different. Optimizing compilers are continually taking advantage of undefined behavior to do ever more surprising things as they get more complex. – Mark Ransom Nov 10 '15 at 22:46