c++member-pointerspointer-conversion

Why is it not allowed to cast Derived T::* to Base T::*?


Background: Many functional languages support algebraic data types, which can to a degree be emulated with virtual functions and inheritance.

The most obvious solution involves a heap allocation, since the derived types come in different sizes. However, we should be able to use a union to hold even the largest type on the stack without any extra allocation. This requires an additional pointer-to-base to be stored along with the union, and at the same time complicates copying and assignment.

It's compelling to solve the latter issue by storing a member selector as an offset from the start of the union that points to the active union member. C++ has member pointers which seem almost fit for the purpose, except that the pointer to each member will have a different type.

Question: Why is it not allowed to cast Derived T::* to Base T::*?

Here's a toy example, unrelated to the above, that bumps into the same limitation:

struct fish {};
struct shark : public fish {};
struct trout : public fish {};

struct aquarium
{
    shark shark;
    trout trout;
};

fish aquarium::* pick_dinner(bool dangerous = true)
{
    if (dangerous)
    {
        return &aquarium::shark;
    }
    return &aquarium::trout;
}

#include <iostream>

void cook(fish&)
{
    std::cerr << "Do it yourself\n";
}

int main()
{
    aquarium spherical, hexagonal;
    fish aquarium::*ingredient = pick_dinner();
    cook(spherical.*ingredient);
    cook(hexagonal.*ingredient);
}

The generated compile error:

main.cpp:15:16: error: cannot initialize return object of type 'fish aquarium::*' with an rvalue of type 'shark aquarium::*'
        return &aquarium::shark;
               ^~~~~~~~~~~~~~~~
main.cpp:17:12: error: cannot initialize return object of type 'fish aquarium::*' with an rvalue of type 'trout aquarium::*'
    return &aquarium::trout;
           ^~~~~~~~~~~~~~~~
2 errors generated.

Solution

  • Why is it not allowed to cast Derived T::* to Base T::*?

    Because it's not allowed by the language standard. The only pointer to member conversion which is allowed, except null member pointer conversion, is the conversion of type cv T Base::* to cv T Derived::*, as stated by Standard part 4.11/2.

    I don't know the exact the reason for it, but corresponding Core language issue 794 was rejected due to "no consensus to make this change at this point in the standardization process". Recent activity on this issue indicates that it may change in the next C++ standard (C++17).