Downcasting Using the 'Static_Cast' in C++

Downcasting using the 'static_cast' in C++

Using static_cast to cast an object to a type it doesn't actually have yields undefined behavior. The symptoms of UB vary widely. There's nothing that says UB can't allow the derived member function to be called successfully (but there's nothing that guarantees that it will, so don't count on it).

Here is the rule for downcasting using static_cast, found in section 5.2.9 ([expr.static.cast]) of the C++ standard (C++0x wording):

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 from B, if a valid standard conversion from "pointer to D" to "pointer to B" exists, 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 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 result of the cast is undefined.

Can I use static_cast to down casting?

Some sites say...

Don't mind what some sites say. The standard (working draft) says:

An lvalue of type “cv1 B”, where B is a class type, can be cast to type “reference to cv2 D”, where D is a class derived from B, if cv2 is the same cv-qualification as, or greater cv-qualification than, cv1.

Also, it contains an example that is almost the same you are asking for:

struct B { };
struct D : public B { };
D d;
B &br = d;

static_cast<D&>(br); // produces lvalue to the original d object

Therefore I would say that yes, you can do that.

There are plenty of uses for such a cast. As an example, CRTP idiom is a case where you know exactly what's the type of the derived class and you don't want to check if the cast is valid through the use of dynamic_cast. Your case is probably another one of those (it's hard to say without the original code).

Why does static_cast allow downcasts when logically it should refuse them for safety purposes or static_cast is not about safety?

I thought static_cast was all about safety (that C style cast were unable to provide)

static_cast is safer than a C-style cast. But not because it's impossible to go wrong with it. It's safer because it's only less likely to go wrong. When we write a C-style cast, a compiler will go through this list to appease us:

When the C-style cast expression is encountered, the compiler attempts
to interpret it as the following cast expressions, in this order:

  1. const_cast<new_type>(expression);
  2. static_cast<new_type>(expression), with extensions: pointer or reference to a derived class is additionally allowed to be cast to pointer or reference to unambiguous base class (and vice versa) even if the base class is inaccessible (that is, this cast ignores the private inheritance specifier). Same applies to casting pointer to member to pointer to member of unambiguous non-virtual base;
  3. static_cast (with extensions) followed by const_cast;
  4. reinterpret_cast<new_type>(expression);
  5. reinterpret_cast followed by const_cast.

The first choice that satisfies the requirements of the respective
cast operator is selected, even if it cannot be compiled (see
example). If the cast can be interpreted in more than one way as
static_cast followed by a const_cast, it cannot be compiled. In
addition, C-style cast notation is allowed to cast from, to, and
between pointers to incomplete class type. If both expression and
new_type are pointers to incomplete class types, it's unspecified
whether static_cast or reinterpret_cast gets selected.

The point in favoring static_cast to that, is that you have a finer grained control over the resulting cast, which does grant a measure of added safety. But it doesn't change the fact that the C++ object model requires that static_cast support casting even when undefined behavior is possible. Only dynamic_cast (not on the above list, by the way) has an added bit of safety for polymorphic types, but that's not without overhead.

C++ static_cast downcast validity

static_cast for performing downcast does not perform any safety checks. Since it's possible for a Base& to be referencing an instance of A, the cast proceeds and since it's NOT actually referencing an A, we enter undefined behavior territory*.

A dynamic_cast on the other hand is safer. It will perform a check and throw an exception in the case of reference casting, or return nullptr in the case of pointer casting.

However, since your base class lacks any virtual functions, dynamic_cast is impossible, so you'd need to modify it with at least a virtual destructor:

class Base{
public:
int m_object;
virtual ~Base()=default;
};

Now if we tried to cast:

A* a = dynamic_cast<A*>(&base);
if (a)
a->arggh();
else
std::cout << "null\n";

Our output would be

null


*Relevant standardese can be found in [expr.static.cast]:

[for a cast like static_cast<D&>(b) where b is an instance of a base class for D], If the object of type “cv1 B” is actually a base class subobject of an object of type D, the result refers to the enclosing object of type D. Otherwise, the behavior is undefined.

Relevant standardese at [expr.dynamic.cast]

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 of a type that would match a handler of type
std::bad_cast

downcasting using static_cast - pointers and objects

In the first one, you are saying "I have a pointer, and I promise it's pointing at a der, so please just go with it".1

In the second one, you can make no such promise, because you unambiguously have a base, not a der.


1. Of course, because it doesn't actually point at a der, you'll get undefined behaviour at runtime.

Why is this value downcast (static_cast to object type) allowed in C++20?

It is legal in C++20.

[expr.static.cast]/4:

An expression E can be explicitly converted to a type T [...] if T is an aggregate type ([dcl.init.aggr]) having a first element x and there is an implicit conversion sequence from E to the type of x.

[dcl.init.aggr]/2:

The elements of an aggregate are: [...] for a class, the direct base classes in declaration order, followed by the direct non-static data members ([class.mem]) that are not members of an anonymous union, in declaration order.

So Derived is an aggregate whose first element is Base, and since C++20, it is permitted to create an aggregate from its first element.

This feature is introduced in P1975R0 "Fixing the wording of parenthesized aggregate-initialization".

static_cast dangling reference when downcasting

It is undefined behaviour, but not because of a dangling reference. The act of casting an object using static_cast from a parent class to the child class when it is not actually that child is undefined behaviour:

 B& b = f(a); // Casts `A` type to `B` incorrectly

See the relevant standard quote here.

Example of useful downcast with static_cast which does not produce undefined behaviour

A very basic example:

class Animal {};
class Dog : public Animal {};

void doSomething() {
Animal *a = new Dog();
Dog *d = static_cast<Dog*>(a);
}

A more contrived example:

class A { public: int a; };
class B : public A {};
class C : public A {};
class D : public B, public C {};

void doSomething() {
D *d = new D();
int bValue = static_cast<B*>(d)->a;
int cValue = static_cast<C*>(d)->a;

// This does not work because we have two a members in D!
// int eitherValue = d->a;
}

There are also countless other cases where you know the actual type type of the instance due to some flags or invariants in your program. However, many sources recommend preferring dynamic_cast in all cases to avoid errors.

Is it undefined behavior to static_cast down a type that isn't actually the type of the object?

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


Related Topics



Leave a reply



Submit