0

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)
}
St.Antario
  • 26,175
  • 41
  • 130
  • 318
  • `B* bp = (B*)&d;` - this is a `reinterpret_cast` so I expect all bets are off for any other uses of `bp` (although I'm not sure under exactly which clause of the standard it comes). This pointer might not point to the `B` subobject of `D`. – M.M Aug 03 '14 at 09:13
  • @MattMcNabb That's a `static_cast`-like cast to an inaccessible base, not a `reinterpret_cast`. – T.C. Aug 03 '14 at 16:47
  • @TC OK that's some good trivia to know.. there are valid C-style casts that cannot be rewritten as C++-style casts – M.M Aug 03 '14 at 20:27

2 Answers2

4

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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
1

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!

Community
  • 1
  • 1
Marco A.
  • 43,032
  • 26
  • 132
  • 246
  • `reinterpret_cast` does not bypass access qualification (if that's what you mean) nor does it respect inheritance. It just casts raw pointer value with no adjustment or arithmetic at all. You are not allowed to use the result for anything, except for special circumstances. Safety goes out the window when you have a raw `new` expression. – Potatoswatter Aug 03 '14 at 11:15
  • @Potatoswatter I can reword the post if that's not clear. Didn't mean to say that it is its only function, this should be more clear. If it isn't let me know. Thanks – Marco A. Aug 03 '14 at 11:18
  • The UB in the second code block is because `B` doesn't have a virtual destructor, rather than because of the accessibility of the base. – T.C. Aug 03 '14 at 16:51