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!