Ad N
Ad N

Reputation: 8396

Are there cases where downcasting an actual Base to a Derived would be defined?

In the general case, it is (a very well deserved) Undefined Behavior to dowcast from a (dynamic) Base to one of the deriving classes Derived

The obvious UB

class Base
{
public:
    virtual void foo()
    { /* does something */ }

    int a;
}

class Derived : public Base
{
public:
    virtual void foo()
    { /* does something different */ }

    double b;
}

Base obj;
Derived derObj = *static_cast<Derived *>(&obj);  // <- here come the demons

In the current implementation approach of compilers, here there would obviously be at least the problems of inconsistent values in the Vtable and b containing garbage values. So it makes sense the standard does not define the behavior of a downcast in those conditions.

The not so obvious naive case

Yet I was curious to know if there were some concessions to this rule in specific cases ? For an example :

class Base
{
public:
    void foo()
    { /* does something */ }

    int a = 1;
    double b = 2.;
}

class DerivedForInt : public Base
{
    int getVal()
    { return a }
}

Base obj;
DerivedForInt derObj = *static_cast<DerivedForInt *>(&obj);  // <- still an UB ?

Here we can easily imagine compiler doing the right thing. But from the standard perspective, is it still undefined ?

Edit : static_cast is a random choice for illustration purpose, it is also interesting if working with other casts !

Upvotes: 10

Views: 503

Answers (3)

user1781290
user1781290

Reputation: 2874

Ok, I'll probably get shred into pieces for this answer...

Obviously, as the other answers stated this is undefined behaviour, as found in the standard. But if your Base class has standard layout and your DerivedForInt class does not add new data members it will have the same (standard) layout.

Under these conditions your cast should cause no troubles even it being UB. According to one of the sources, it is at least safe to do a

DerivedForInt *derived = reinterpret_cast<DerivedForInt*>(&base.a);

Sources:

What are Aggregates and PODs and how/why are they special?

PODs and inheritance in C++11. Does the address of the struct == address of the first member?

From the second link:

Here's the definition, from the standard section 9 [class]:

A standard-layout class is a class that:

  • has no non-static data members of type non-standard-layout class (or array of such types) or reference,
  • has no virtual functions (10.3) and no virtual base classes (10.1),
  • has the same access control (Clause 11) for all non-static data members,
  • has no non-standard-layout base classes,
  • either has no non-static data members in the most derived class and at most one base class with non-static data members, or has no base classes with non-static data members, and
  • has no base classes of the same type as the first non-static data member.

And the property you want is then guaranteed (section 9.2 [class.mem]):

A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa.

This is actually better than the old requirement, because the ability to reinterpret_cast isn't lost by adding non-trivial constructors and/or destructor.

Upvotes: 8

ForEveR
ForEveR

Reputation: 55897

n3376 5.2.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 class derived (Clause 10) from B if a valid standard conversion from “pointer to D” to “pointer to B” exists (4.10), 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 null pointer value (4.10) is converted to the null pointer value of the destination type.

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

Since &obj is not points to DerivedForInt it's UB.

Upvotes: 3

Agentlien
Agentlien

Reputation: 5136

This is still undefined behaviour and I believe it should be.

Why it is undefined

As provided by @ForEveR in his answer:

n3376 5.2.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 class derived (Clause 10) from B

...

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

Why it should be undefined

It would only work for POD types, since adding a virtual function to your base is enough for this to hurt you in all compilers I know of. Also, the difference between types may be conceptual, not just in their data layout. Type safety is just as much about providing strong abstractions as it is about preventing issues with data representation.

If you would like something like this, it seems better to provide it as an ordinary function or to add a constructor in the derived class which takes an instance of the base class.

Upvotes: 2

Related Questions