2

Quoted from C++ Primer 5th 19.2.1. The dynamic_cast Operator

A dynamic_cast has the following form:

dynamic_cast<type*>(e)
dynamic_cast<type&>(e)
dynamic_cast<type&&>(e)

where type must be a class type and (ordinarily) names a class that has virtual functions. In the first case, e must be a valid pointer (§ 2.3.2, p. 52); in the second, e must be an lvalue; and in the third, e must not be an lvalue.

In all cases,the type of e must be either a class type that is publicly derived from the target type, a public base class of the target type, or the same as the target type. If e has one of these types, then the cast will succeed. Otherwise, the cast fails.
If a dynamic_cast to a pointer type fails, the result is 0. If a dynamic_cast to a reference type fails, the operator throws an exception of type bad_cast

However,here I've written a code snippet:

struct A {};
struct B : private A // note: *private* inheritance
{
  A* test() {
    return dynamic_cast<A*>(this);
  }
};

int main()
{
  B b;
  if(b.test()==nullptr)
      throw 1;
}

In the code snippet above, A is just a private base of B, which is not taken into account by the c++ primer. However, this code snippet could be compiled and run without error. Has the primer made a mistake?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
choxsword
  • 3,187
  • 18
  • 44
  • I don't get it, what's the matter? – YSC Mar 20 '18 at 10:20
  • @YSC According to primer,it the return of `test()` should be 0 . – choxsword Mar 20 '18 at 10:21
  • @Quentin Was going to comment the same. But to be honest, it’s ridiculous that compilers aren’t forced to make this an error. – Konrad Rudolph Mar 20 '18 at 10:21
  • @KonradRudolph throw in `-Werror` and you're good :) – Quentin Mar 20 '18 at 10:22
  • The C++ primer made the mistake of trying to list all cases where `dynamic_cast` will succeed, and missed one. Within member functions or friends of `B`, the base class `A` is accessible, so the `dynamic_cast` can succeed. – Peter Mar 20 '18 at 10:22
  • Note that `dynamic_cast` throws an exception *only* if trying to cast to a *reference*. – Some programmer dude Mar 20 '18 at 10:23
  • 2
    *"According to primer,it should throw a exception."* - no (at least not in the bit you quote). Rather, the Primer says a failing cast to a reference type will throw. Failing casts to pointer types return `nullptr`. – Tony Delroy Mar 20 '18 at 10:24
  • @TonyDelroy But it's not `nullptr` in this case,either. – choxsword Mar 20 '18 at 10:27
  • 1
    The "primer" just pulled the bolded passages out of thin air. They are plainly wrong. The standard says nothing of this sort. – n. m. could be an AI Mar 20 '18 at 10:40
  • @bigxiao: the Primer said failing dynamic casts to references throw - true. We've told you failing casts to pointers return `nullptr`. Both true. The Primer tells you your code should fail - *that's* untrue. – Tony Delroy Mar 20 '18 at 11:00
  • @Quentin Oh I do that anyway. It’s annoying because it’s a frequent cause of errors here on Stack Overflow (and probably in quite a few code bases) and there’s no reason at all to allow it. – Konrad Rudolph Mar 20 '18 at 12:19

3 Answers3

7

This is all in all an unfortunate phrasing on the primers part. It bunched the two types of casts one can do into one sentence, and then misspoke as a result.

Casting to a base class, doesn't require a runtime cast operation. It is, as T.C. says, purely a static construct. And like T.C. quoted, it requires an accessbile base, not public one. So your code is all good and well.

For a runtime cast (a downcast) the C++ standard places a requirement on the operand and the types involved in a dynamic cast in order for it to succeed. The class must be publicly derived, otherwise the implementation isn't obligated to make a successful cast down the inheritance chain. I mean, it could in theory make the cast successful, but according to the specification "the runtime check fails", which doesn't leave much leeway.

But either way there's nothing wrong in your program that would make it fail to compile, nor is there anything there that would cause any sort of runtime error.


If we change your code to cast down, and not cast up, here's an example that doesn't even build:

struct A {};
struct B : private A // note: *private* inheritance
{
  A* test(B* p) {
    return dynamic_cast<A*>(p);
  }

  friend B* foo(A*);
};

B* foo(A* a) {
    return dynamic_cast<B*>(a);
}

int main()
{
  B b;
  *foo(&b);
}

A is an accessible base of B in foo, and yet, the cast is ill-formed.


The minimal change which will bring the primer back on course is to turn "a class type that is publicly derived from the target type" into "a class type that is accessibly derived from the target type". Since there's nothing of the sort in the publicly available errata, we can guess it's an editorial mistake that is yet to be pointed out.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
6

A derived-to-base dynamic_cast is static, not dynamic, and only requires the base to be accessible in the context of the cast (and unambiguous). See [expr.dynamic.cast]/5.

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

The bolded passages are all obviously wrong.

The dynamic cast expression that deals with class types can be logically subdivided in two cases.

  • type is a class type which is a base class of the statically determined type of e. In this case dynamic_cast is more or less synonymous with static_cast. In particular, type must be an accessible and unambiguous (but not necessarily public) base class of the type of e.
  • type is a class type which is not a base class of the statically determined type of e. In this case, a runtime check takes place. This is further subdivided to the downcast and crosscast cases. The differences between those are only important in the case of multiple inheritance and/or non-public inheritance. In either of these cases, type must be a type of some subobject of the full object of e.

The primer never says whether it means static or dynamic type of e, but in either case the description is completely wrong. For the cast to succeed,

  • type does not need to be related to the static type of e in any way
  • type must be a base class of the dynamic type of e or that type itself (but not a proper derived class of it), with further restrictions related to accessibility and non-ambiguity.
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243