Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 !

like image 900
Ad N Avatar asked Nov 28 '13 10:11

Ad N


People also ask

Why the concept of downcasting is not allowed?

Downcasting is not allowed without an explicit type cast. The reason for this restriction is that the is-a relationship is not, in most of the cases, symmetric. A derived class could add new data members, and the class member functions that used these data members wouldn't apply to the base class.

Is downcasting possible in C++?

Dynamic Cast: A cast is an operator that converts data from one type to another type. In C++, dynamic casting is mainly used for safe downcasting at run time. To work on dynamic_cast there must be one virtual function in the base class.

What is the use of downcasting in C++?

The Downcasting is an opposite process to the upcasting, which converts the base class's pointer or reference to the derived class's pointer or reference. It manually cast the base class's object to the derived class's object, so we must specify the explicit typecast.

Is used for downcasting base class pointers?

A dynamic cast expression is used to cast a base class pointer to a derived class pointer. This is referred to as downcasting.


2 Answers

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.

like image 184
user1781290 Avatar answered Sep 28 '22 01:09

user1781290


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.
like image 25
ForEveR Avatar answered Sep 28 '22 01:09

ForEveR