Search code examples
c++pointersc++14

Conversion of Pointer to Pointer Types


When working with inheritance, implicit conversions allow "convenient" upcasting. For example:

class A{};
class B : public A{};

int main()
{
    B b;
    A* pa = &b; // Implicit conversion from B* to A*
    return 0; 
}

The same does not hold true for pointers to pointers. For example:

int main()
{
    B* pb = &b;
    B** ppb = &pb;
    
    A** ppa0 = ppb; // cannot initialize a variable of type 'A **' with an lvalue of type 'B **'
    A** ppa1 = &pb; // cannot initialize a variable of type 'A **' with an rvalue of type 'B **'
    A** ppa2 = static_cast<A**>(&pb); // static_cast from 'B **' to 'A **' is not allowed
    A** ppa3 = &static_cast<A*>(pb); // cannot take the address of an rvalue of type 'A *'
    return 0; 
}

The last error made me think that this behavior is probably related to the strict aliasing rule. On the other hand, there is an exemption for the case when a value is accessed through a base class type. So why should a A** pointer not be allowed to hold the address of a B** pointer?


Solution

  • (I didn't notice the C++14 tag, my answer assumes C++17 or later.)

    That you can cast a B* to an A* and access the result as an A object is not because there is an exemption in the aliasing rule. There is no such exemption. (Note that the aliasing rule you refer to has been fixed in later C++ revisions.)

    For general B derived from some A, if you actually did cast a B* to a A* with a reinterpret_cast, the pointer value would generally still refer to the original B object and then the aliasing rule would apply and the access through the result would cause undefined behavior. Except when there is an object of the target type which is pointer-interconvertible with the object that the original pointer value refers to (which can happen for standard layout base classes such as in your trivial example), reinterpret_cast will not change the pointer value at all. reinterpret_cast then only affects the type of the expression, but not its value. (Or even worse, it may produce an unspecified value if there is alignment mismatch.)

    However, a static_cast or implicit conversion from B* to A* is allowed. But that only works because it actually changes the value of the pointer from a pointer to some B object to a pointer to some A object, namely the A subobject of the B object. This may or may not involve a change of the address represented by the pointer value as well, but regardless, the important part is that the resulting value of type A* actually points to an object of type A.

    A** is a pointer to an object of type A* and B** is a pointer to an object of type B*. If you intent to cast the B** to an A** in a useful way, then you would want that dereferencing the result twice will give an lvalue referring to some A object. But that would require not only casting the double pointer's value, but also changing the value of the (simple) pointer object that the value points to. Such a behavior would be very unexpected. A cast doesn't usually change any object's value.

    When you use reinterpret_cast or a C-style cast to cast B** to A**, then you actually don't change the original B** pointer value and as a consequence you will get a A** that actually points to a B* object and again, because there is no exemption for A*/B*, accessing through the resulting pointer will cause undefined behavior for violating the aliasing rule.