3

Here is an discussion on stackoverflow of the four kinds of explicitly cast. But I've come across a question in the most-voted answer.

Quoted from the most-voted wiki answer:

static_cast can also cast through inheritance hierarchies. It is unnecessary when casting upwards (towards a base class), but when casting downwards it can be used as long as it doesn't cast through virtual inheritance. It does not do checking, however, and it is undefined behavior to static_cast down a hierarchy to a type that isn't actually the type of the object.

But in cppref,I read something less severely:
static_cast < new_type > ( expression )

If new_type is a pointer or reference to some class D and the type of expression is a pointer or reference to its non-virtual base B, static_cast performs a downcast. This downcast is ill-formed if B is ambiguous, inaccessible, or virtual base (or a base of a virtual base) of D. Such static_cast makes no runtime checks to ensure that the object's runtime type is actually D, and may only be used safely if this precondition is guaranteed by other means.

So in cppref it does not says undefined behavior,but instead less severely as not safe .

So when I do something like:

class A{virtual foo(){}};
class B:public A{};
class C:public B{};
int main()
{
  C*pc=new C;
  A*pa=static_cast<A*>(pc);//Ok,upcast.
  B*pb=static_cast<B*>(pa);//downcast,**is it undefined or just not safe?**
  C* pc1=static_cast<C*>(pb);//downcast back to C;
}

One more question,if it's not UB,is it UB to dereference pb ?

choxsword
  • 3,187
  • 18
  • 44
  • In the code you show, all casts are valid downcasts. – Some programmer dude Mar 21 '18 at 06:42
  • It *is* a perfectly valid defined behaviour, albeit useless. Because dereferencing that pointer will be an UB. – GreenScape Mar 21 '18 at 06:42
  • @Someprogrammerdude But what I quoted said it's UB,does I get something wrong or the quoted paragraph is not true? – choxsword Mar 21 '18 at 06:43
  • @GreenScape Do you mean that dereferencing `pb` is UB?So it is valid until I dereference the pointer `pb` ? – choxsword Mar 21 '18 at 06:44
  • `B` is a non-virtual base of `C`. So it can be seen as an *upcast* from `pc` to `pb`. At least I think so. If you want an authoritative answer you should add the `language-lawyer` tag and wait for an answer with a quote from the standard. – Some programmer dude Mar 21 '18 at 06:46
  • @bigxiao, exactly. Pointer type is just another built-in N-byte *kind of integer* type. You can use it just to store arbitrary data. Its perfectly valid to do so. But as cppref mentioned, to actually be able to use it (dereference) you have to guaranteed that pointer points to a valid and correct object. – GreenScape Mar 21 '18 at 06:46
  • @GreenScape But `pc`, `pa`, `pb` and `pc1` are all pointing to a valid object of type `C` (which is a `B` which is an `A`), so why should it be UB to dereference any pointer? – Some programmer dude Mar 21 '18 at 06:47
  • 4
    @bigxiao You are reading too much into the choice of words on cppref. "Not safe" is not formal, unlike UB. – StoryTeller - Unslander Monica Mar 21 '18 at 06:48
  • @Someprogrammerdude, all examples of OP are perfectly valid and not an UB. I was talking generally. Sorry for conusion. – GreenScape Mar 21 '18 at 06:49
  • @StoryTeller I've thought the cppref is authoritive ,formal and strict on its description,since most of its content is quoted from the standard. – choxsword Mar 21 '18 at 06:51
  • 4
    @bigxiao - Cppref is the best reference for C++ on the web. But it's not the standard, nor is it subject to the same required rigor. It's also much easier to fix if it contains a problematic wording. – StoryTeller - Unslander Monica Mar 21 '18 at 06:52

2 Answers2

7

cppreference is written in English with the intent to convey good understanding, it's not actually specification. But I have no problem with its wording here:

Such static_cast makes no runtime checks to ensure that the object's runtime type is actually D, and may only be used safely if this precondition is guaranteed by other means.

If you guarantee the precondition, it's fine. If you don't guarantee the precondition, then your code isn't safe. What does it mean for code to not be safe? Its behavior isn't defined.

The actual specification uses this wording:

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 behavior is undefined.


Either way, in your example, all of your casts are valid.

B*pb=static_cast<B*>(pa);//downcast,**is it undefined or just not safe?**

You're missing the condition. A downcast isn't undefined behavior, in of itself. It's only undefined behavior if there isn't actually an object of derived type there. And in this case pa is pointing to a C, which is a B, so the cast to a B is safe.

This, however, is not:

struct B { };
struct D1 : B { };
struct D2 : B { };

B* p = new D1;
static_cast<D2*>(p); // undefined behavior, no D2 object here
Barry
  • 286,269
  • 29
  • 621
  • 977
2

It's well defined behavior.

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 behavior is undefined.

(C++14 [expr.static.cast] (§5.9) ¶11, emphasis added)

pa points to an object of type A which is actually a subobject of a B, so the second cast is fine and the result points to a valid B. Your third cast is OK for the same reason (pb points to a B subobject of a C).

The "not safe" bit expressed by cppreference is about the fact that there are no safety nets here: you have to know by your own means if the actual dynamic type of the pointed object is compatible with the cast you ask for; if you get it wrong there's no std::bad_cast or nullptr - you get bad old undefined behavior.

Matteo Italia
  • 123,740
  • 17
  • 206
  • 299