Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Upcasting a pointer to data member and its polymorphic behavior

Tags:

c++

pointers

I am trying to cast a pointer to data member of derived class to a pointer to data member of base class, but the following code does not compile:

class Base 
{
public:
    virtual void f() {}
};

class Derived : public Base 
{
public:
    void f() override {}
};

class Enclosing
{
public:
    Derived member;
};

int main()
{
    Derived Enclosing::*p = &Enclosing::member;
    auto bp = static_cast<Base Enclosing::*>(p); // compile error
}

So I used reinterpret_cast instead, and the code compiles:

auto bp = reinterpret_cast<Base Enclosing::*>(p); // passes compile

And I tried to use bp straightforwardly:

Enclosing instance;
(instance.*bp).f(); // calls Base::f

This is not what I expected because the member in Enclosing is actually Derived type. Then I tried this:

(&(instance.*bp))->f(); // calls Derived::f

It works on my environment, but is this behavior guaranteed?

like image 986
rolevax Avatar asked Feb 02 '18 10:02

rolevax


1 Answers

You cannot have what you want. You can get polymorphism with data members only in this situation: pointer to member of D of type T converted to pointer of member of B of type T where B is a base class of D.

What you want, converting pointer to member of X of type T1 to pointer to member of X of type T2 is not allowed or Undefined Behavior.

To achieve polymorphism you need something like this:

Enclosing e;
Derived Enclosing::* d = &Enclosing::d;

Base* b = &(e.*d);
return b->foo();    // calls Derived::foo

§4 Standard conversions [conv]

Standard conversions are implicit conversions with built-in meaning.

§4.11 Pointer to member conversions [conv.mem]

  1. A null pointer constant (4.10) can be converted to a pointer to member type. [...]

  2. A prvalue of type “pointer to member of B of type cv T”, where B is a class type, can be converted to a prvalue of type “pointer to member of D of type cv T”, where D is a derived class (Clause 10) of B. [...]

So we see that for standard conversion (implicit conversions) your conversion is not allowed

§5.2.9 Static cast [expr.static.cast]

  1. A prvalue of type “pointer to member of D of type cv1 T” can be converted to a prvalue of type “pointer to member of B” of type cv2 T, where B is a base class (Clause 10) of D, if a valid standard conversion from “pointer to member of B of type T” to “pointer to member of D of type T” exists (4.11), and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. [...]

For static_cast we see that basically only the standard conversion are allowed for pointer to member casts. Which is what you observed, the static_cast is a compiler error.

§ 5.2.10 Reinterpret cast [expr.reinterpret.cast]

A prvalue of type “pointer to member of X of type T1” can be explicitly converted to a prvalue of a different type “pointer to member of Y of type T2” if T1 and T2 are both function types or both object types. [...]. The result of this conversion is unspecified, except in the following cases:

(10.1) converting a prvalue of type “pointer to member function” to a different pointer to member function type and back to its original type yields the original pointer to member value.

(10.2) converting a prvalue of type “pointer to data member of X of type T1” to the type “pointer to data member of Y of type T2” (where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer to member value.

For reinterpret_cast we see the cast is allowed (as you've seen there is no compiler error. However the result is unspecified, except in the 2 mentioned cases which imply converting back to the original value. It doesn't apply to our situation, which means that your code with reinterpret_cast has Undefined Behavior.

Furthermore

§5.5 Pointer-to-member operators [expr.mptr.oper]

Abbreviating pm-expression .*cast-expression as E1.*E2, E1 is called the object expression . If the dynamic type of E1 does not contain the member to which E2 refers, the behavior is undefined.

This is proof that instance.*bp is valid iff the object pointed by bp must exist in instance. That implies the type of bp.

like image 62
bolov Avatar answered Oct 22 '22 02:10

bolov