Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is upcasting illegal in 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
    

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;
};

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;
    }