St.Antario
St.Antario

Reputation: 27385

Failed and ill-formed casts

could you possibly explain what is difference between ill-formed cast and failed cast. For instance:

class A { virtual void f(); };
class B { virtual void g(); };
class D : public virtual A, private B { };

void g() {
    D d;
    B* bp = (B*)&d;    // cast needed to break protection
    A* ap = &d;        // public derivation, no cast needed
    D& dr = dynamic_cast<D&>(*bp);    // fails
    ap = dynamic_cast<A*>(bp);        // fails
    bp = dynamic_cast<B*>(ap);        // fails
    ap = dynamic_cast<A*>(&d);        // succeeds
    bp = dynamic_cast<B*>(&d);        // ill-formed (not a run-time check)
}

Upvotes: 0

Views: 165

Answers (2)

Marco A.
Marco A.

Reputation: 43662

Let's start from the beginning and take a look at every case:

class A { virtual void f() {} };
class B { virtual void g() {} };
class D : public virtual A, private B { };

void g() {
  D d;
  B* bp = (B*)&d;

  [continue...]

C++ casts (exception for reinterpret_cast which just casts raw pointer value with no adjustment or arithmetic at all) aren't allowed to "ignore" inheritance access levels (https://stackoverflow.com/a/3674955/1938163), C-style casts can.

With the above code bp is a pointer to a valid object, but the access level is bypassed completely. Could this be a problem?

The answer is yes, take the following as an example:

class A { virtual void f() {} };
class B { virtual void g() {}
public:
    ~B() {cout << "B's destructor";} // You can destroy B objects but NOT D objects from B* pointers
};
class D : public virtual A, private B {
    ~D() {cout << "D's destructor";}
};

void g() {
    D *d = new D();

    B* bp = (B*)d; // Bypass access permissions

    delete bp; // This shouldn't happen! D's destructor will NOT be called! Undefined Behavior!

Compiling with -Wold-style-cast -Werror avoids this problem (and several others as well: https://stackoverflow.com/a/12765440/1938163)

Continuing with your example we have

A* ap = &d;

and this is a perfectly legit upcast. What is not legit is the following cast:

D& dr = dynamic_cast<D&>(*bp);

Citing from the standard and substituting some words for readability reasons:

(N3690 - §5.2.7 - 8)

If D is the class type to which D& points or refers, the run-time check logically executes as follows: — If, in the most derived object pointed (referred) to by bp, bp points (refers) to a public base class subobject of a D object, and if only one object of type D is derived from the subobject pointed (referred) to by bp the result points (refers) to that D object.

thus the access permissions are wrong and the cast fails. It is NOT ill-formed, just fails (read later for the difference).

Had you asked for a pointer you would have got a NULL one, but since a reference needs to be bound to an object the above throws an exception (it's the only sensible thing to do here).

The cast that follows also isn't ill-formed but just plain wrong. Usually you can't cast from a base pointer to another base (https://stackoverflow.com/a/7426562/1938163) but since the base classes involved here are polymorphic, the following should be allowed and valid:

ap = dynamic_cast<A*>(bp);

... if bp points (refers) to a public base class subobject of the most derived object, and the type of the most derived object has a base class, of type A, that is unambiguous and public, the result points (refers) to the A subobject of the most derived object.

but again: the permissions are getting things wrong and the cast is failing (NOT ill-formed, again just failing).

The cast that follows is already an invalid cast since ap is NULL

bp = dynamic_cast<B*>(ap);

but if ap weren't NULL, the cast would have failed anyway for the same passage cited above:

... if ap points (refers) to a public base class subobject of the most derived object, and the type of the most derived object has a base class, of type B, that is unambiguous and public, the result points (refers) to the B subobject of the most derived object.

The only cast that succeeds is the

ap = dynamic_cast<A*>(&d);

where a D object pointer is casted to a public base class.

The last cast is finally ill-formed

bp = dynamic_cast<B*>(&d);

since according to the standard

(N3690 - §5.2.7 - 5)

(dynamic_cast)

If B* is “pointer to cv1 B” and &d has type “pointer to cv2 D” such that B is a base class of D, the result is a pointer to the unique B subobject of the D object pointed to by &d.

...

In both the pointer and reference cases, the program is ill-formed if cv2 has greater cv-qualification than cv1 or if B is an inaccessible or ambiguous base class of D.

Grand-total: 3 failed casts (one with exception throwing), one succeeded and one ill-formed.

Finally: failed casts are casts which could not be accomplished but they can be handled according to the standard rules:

If the value of v is a null pointer value in the pointer case, the result is the null pointer value of type T. If C is the class type to which T points or refers, the run-time check logically executes as follows:

...(same rules as above)

— Otherwise, the run-time check fails.

The value of a failed cast to pointer type is the null pointer value of the required result type. A failed cast to reference type throws an exception

Unless instructed otherwise (no diagnostics required) a compiler implementation is usually supposed to emit an error or a warning for an ill-formed program, in the casts you're interested in:

B* bp = (B*)&d;
A* ap = &d;
D& dr = dynamic_cast<D&>(*bp); // This is a runtime error
ap = dynamic_cast<A*>(bp); // this is a runtime error
bp = dynamic_cast<B*>(ap); // this is a runtime error
ap = dynamic_cast<A*>(&d); // succeeds
bp = dynamic_cast<B*>(&d); // This is ill-formed and the compiler should warn about it

If I got something wrong (very likely) please write it down in the comments below and I'll fix my post immediately. Thanks!

Upvotes: 1

T.C.
T.C.

Reputation: 137315

Despite the name, when you use dynamic_cast to do an upcast (derived->base), the cast is done at compile time and behaves the same way as a static_cast or an implicit conversion - if the base is ambiguous or inaccessible the program is ill-formed, meaning that the compiler must produce a diagnostic. §5.2.7 [expr.dynamic.cast]/p5:

[For the expression dynamic_cast<T>(v):]

If T is "pointer to cv1 B" and v has type "pointer to cv2 D" such that B is a base class of D, the result is a pointer to the unique B subobject of the D object pointed to by v. Similarly, if T is "reference to cv1 B" and v has type cv2 D such that B is a base class of D, the result is the unique B subobject of the D object referred to by v. 67 The result is an lvalue if T is an lvalue reference, or an xvalue if T is an rvalue reference. In both the pointer and reference cases, the program is ill-formed if cv2 has greater cv-qualification than cv1 or if B is an inaccessible or ambiguous base class of D.

67 The most derived object (1.8) pointed or referred to by v can contain other B objects as base classes, but these are ignored.

In other cases (casting down or sideways), the checks are performed at run-time. If it fails, then the cast result is a null pointer for pointer casts, and a std::bad_cast exception for reference casts.

Upvotes: 4

Related Questions