When is upcasting illegal in C++?

C++ClassBaseDerivedDowncast

C++ Problem Overview


I am pretty sure I understand the general difference between upcasting and downcasting, particularly in C++. I understand that we can't always downcast because casting a base class pointer to a derived class pointer would assume that the base class object being pointed to has all the members of the derived class.

Early in the semester, my professor told the class that it is also sometimes illegal to upcast in C++, but I seem to have missed the reason why in my notes, and I can't remember when this occurs.

When is it illegal to upcast in C++?

C++ Solutions


Solution 1 - C++

If by "illegal" you mean ill-formed, then it is illegal if the base class is inaccessible or ambiguous.

  • It is inaccessible when, for example, the base class is private.

      class A {};
      class B : A {};
      ...
      B b;
      A *pa = &b; // ERROR: base class is inaccessible
    

Note that even in C++11 a C-style cast can "break through" access protection and perform a formally correct upcast

    A *pa = (A *) &b; // OK, not a `reinterpret_cast`, but a valid upcast

This usage should be avoided, of course.

  • It is ambiguous if your source type contains multiple base subobjects of the target type (through multiple inheritance).

      class A {};
      class B : public A {};
      class C : public A {};
      class D : public B, public C {};
    
      D d;
      A *pa = &d; // ERROR: base class is ambiguous
    

In such cases the upcast can be performed by explicitly "walking" the desired upcast path with intermediate upcasts to the point where the base is no longer ambiguous

    B* pb = &d;
    A* pa = pb; // OK: points to 'D::B::A' subobject

Solution 2 - C++

If the base class is ambiguous (inherited two or more times via different paths) then you can’t do an upcast in a single step.

If the base class is inaccessible then the only way to upcast is to use a C style cast. This is a special case of that cast, it's the only one that can do the job. Essentially it then behaves as a static_cast that's not limited by accessibility.


Standardese.

C++11 §5.4/4: > … in [a C cast] performing a static_cast in the following situations the conversion is valid even if the base class is inaccessible:
>

  • a pointer to an object of derived class type or an lvalue or rvalue of derived class type may be explicitly converted to a pointer or reference to an unambiguous base class type, respectively;
  • a pointer to member of derived class type may be explicitly converted to a pointer to member of an unambiguous non-virtual base class type;
  • a pointer to an object of an unambiguous non-virtual base class type, a glvalue of an unambiguous non-virtual base class type, or a pointer to member of an unambiguous non-virtual base class type may be explicitly converted to a pointer, a reference, or a pointer to member of a derived class type, respectively.

Example of ambiguity:

struct Base {};
struct M1: Base {};
struct M2: Base {};
struct Derived: M1, M2 {};

auto main() -> int
{
    Derived d;
    //static_cast<Base&>( d );                      //! Ambiguous
    static_cast<Base&>( static_cast<M2&>( d ) );    // OK
}

Example of inaccessible base, with (usually) address adjustment in the cast:

struct Base { int value; Base( int x ): value( x ) {} };

class Derived
    : private Base
{
public:
    virtual ~Derived() {}       // Just to involve an address adjustment.
    Derived(): Base( 42 ) {}
};

#include <iostream>
using namespace std;

auto main() -> int
{
    Derived d;
    Base& b = (Base&) d;
    cout << "Derived at " << &d << ", base at " << &b << endl;
    cout << b.value << endl;
};

Solution 3 - C++

There are two cases in which upcasting is ill-formed in C++ (diagnosed at compile-time):

  1. The base-class in question is not accessible:

     class base {};
     class derived : base {};
    
     int main() {
         derived x;
         base& y = x; // invalid because not accessible.
         // Solution: C-style cast (as static_cast without access-check)
         base& y1 = (base&)x;
     }
    
  2. The base-class sub-object in question is not unambiguous:

     class base {};
     struct A1 : base {};
     struct A2 : base {};
    
     struct derived : A1, A2 {};
     int main() {
         derived x;
         base& y = x; // invalid because ambiguous.
         // Solution 1, scope resolution:
         base& y1 = static_cast<A1::base&>(x);
         base& y2 = static_cast<A2::base&>(x);
         // Solution 2, intermediate unambiguous steps:
         A1& a1 = x;
         A2& a2 = x;
         base& ya1 = a1;
         base& ya2 = a2;
     }
    

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionConnor FoleyView Question on Stackoverflow
Solution 1 - C++AnTView Answer on Stackoverflow
Solution 2 - C++Cheers and hth. - AlfView Answer on Stackoverflow
Solution 3 - C++DeduplicatorView Answer on Stackoverflow