c++language-lawyerstandardsmember-pointers

Understanding pointer to member of class of type - no polymorphism


I thought it is straightforward that a 'pointer to member of class T of type DerivedT' can be used as a 'pointer to member of class T of type BaseT' if BaseT is base of DerivedT. The analogy seems to be obvious at least to me as DerivedT* can be used as a BaseT*, so DerivedT T::* should be able to be used as BaseT T::*

But it is not the case:

struct BaseT
{
};

struct DerivedT: public BaseT
{
};

struct T 
{
    DerivedT m_test;
};

using BaseTMemPtr = BaseT T::*;

int main()
{
    T test;
    BaseT* simplePtr = &test.m_test; //It is DerivedT*, but can be used as BaseT*
    BaseT (T::*memPtr) = &T::m_test; //Error, BaseT T::* cannot be used as DerivedT T::*
    BaseTMemPtr memPtr2 = &T::m_test; //Error, just the same
}

As I see there are two ways to interpret pointers to class members:

  1. DerivedT T::* is a DerivedT pointer that points to a DerivedT object inside an object of T class (so points an object relative to another object)
  2. DerivedT T::* points to some part of an object of class T which has by the way DerivedT type.

So the main difference between this two ways is while the first one can be interpreted as a kind of DerivedT pointer (enabling polymorphism), the later one kind of discards the type and restrict the usage a lot.

Why did C++ choose the second approach? What could be the unwanted consequence of enabling using DerivedT T::* as a BaseT T::* ? What are pointers to members in practice?

UPDATE: I would like to achieve the following: Desired solution But it does not work if the members are not BaseMember types but BaseMember descendants. The concept works if I use BaseMembers (but in this case I cannot implement the desired member functionality): Works with broken functionality

UPDATE 2: Why
TLDR:
A way to compile time 'mark' (uniquely identify) a non-static member object of a runtime constructed class. Then check if a regular (non-member) pointer was compile-time marked or not in a runtime function that has
1, the compile time array of the marked members (can be anything, in my mind the polymorphic pointers-to-members)
2. 'this' pointer of the containing object (that has the marked and unmarked members)
3, the regular (non-pointer-to-member) pointer to the non-static member object.

Timeline: class definition (compile time) -> add class members (compile time) -> mark class members as enabled - e.g. in an array - (compile time) -> construction (runtime) -> members will call register function (runtime) -> in register function we need to check if the caller (we receive it as a regular pointer) is allowed to call this function or not (runtime).

Long description:
In a library I have a CRTP base class (DataBinding) that the users should descend from if they would like to use its compile- and runtime functionality. Then in the library I also have an interface class: BaseMember, and many derived classes of it. The end user can use the derived classes to add non-static class member objects in their user-defined DataBinding-descendant classes.

In the user code, in DataBinding-descendant user classes the user can have BaseMember based non-static class members. And here comes the new functionality that requires pointer-to-member polymorphism: The user should be able to mark some of BaseMember-based class members in compile time(!) (the class itself does not have constexpr constructor) - in my mind this 'mark' could be storing the pointer-to-member of the BaseMember descendant member object -, and only the marked objects should be allowed to runtime-call a class member function (registerMember) in DataBinding (CRTP base of the current class).

In the registerMember runtime function I have the "this" object pointer (the containing object), I have the compile time user defined list that marks the enabled pointers-to-members (it can be replaced with any kind of unique identification) and I have the actual member pointer. I need to check if the actual member pointer is allowed to call the function (it was marked compile time).


Solution

  • A pointer-to-data-member is normally represented by a simple integer value, telling the offset of the beginning of the owner class to the beginning of the member. So the algorithm of retrieving a data member given a pointer to its owner is as simple as "add the offset to the address and dereference".

    However, in order to go from a pointer-to-derived to a pointer-to-base, such a simple description is not enough. The offset is not necessarily constant, because of virtual inheritance. In order to find the offset, we need an actual derived object. The offset is stored in there somewhere. So in the general case, a pointer-to-data-member would have to be represented as a combination of at least two offsets, and fetching the member would require a decision (is a virtual base present?)

    I guess the standard could have provided such conversion for the non-virtual inheritance case only. In this case the offset is constant and the conversion would consist of adding the two offsets (unless I'm missing some other corner case). However this is not being done, and the reason is, of course, that no one felt motivated enough to write a proposal. Pointers to data members (as opposed to pointers to member functions) were originally considered by Stroustrup "an artifact of generalization rather than something genuinely useful" (D&E, 13.11 Pointers to Members). They are now being used (mostly) to describe class layouts in an implementation-independent way, but there is no real need to use them polymorphically.