3

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).

Broothy
  • 659
  • 5
  • 20
  • 1
    because it is `DerivedT` not `BaseT`? – apple apple Oct 16 '21 at 07:52
  • Possible duplicate [Pointer to class data member "::\*"](https://stackoverflow.com/questions/670734/pointer-to-class-data-member) – apple apple Oct 16 '21 at 07:54
  • @appleapple DerivedT (T::*) and BaseT (T::*), and as their name say pointer to members. So their type are definitely not DerivedT and BaseT, but some kind of pointers to DerivedT and BaseT. A regular pointer to DerivedT is a BaseT pointer, this is why I think a DerivedT member pointer [DerivedT (T::*)] should be a BaseT member pointer [BaseT (T::*)]. It is not the case, but I don't understand the reason why it is forbidden. – Broothy Oct 16 '21 at 08:00
  • 2
    *"as `DerivedT*` is a `BaseT*`"* Let me stop you right there. This is ***not*** correct. In most cases you may be able to use `DerivedT*` in a place where `BaseT*` is needed, but not in all cases and certainly not the other way round. Let's consider the case where you use a pointer to the first element of a array and furthermore let's assume `DerivedT` has a larger size than `BaseT`. `DerivedT* array = ...; BaseT* arrayAsBase = array;`: you can use `array[5]` to refer to the 6th element of the array, but accessing `arrayAsBase[5]` is undefined behaviour. – fabian Oct 16 '21 at 08:25
  • @fabian you are right, let me rephrase it: One can use a DerivedT* as a BaseT*. You are right with the corner cases but the code above is quite straightforward. – Broothy Oct 16 '21 at 08:31
  • @Broothy I think it's probably the same as convert `Derived& (*)(T)` to `Base& (*)(T)`. – apple apple Oct 16 '21 at 09:28
  • @appleapple Functions and objects are handled differently all across the standard. The whole footprint is part of the function's 'type'. Only object pointers have this 'DerivedT* is a BaseT*' rule. Such a rule doesn't exist for function pointers (and would make no sense IMO), but on member pointers I think it would make sense. – Broothy Oct 16 '21 at 09:43
  • 2
    @Broothy the standard does not specify how pointer to member are implemented IIRC. So it's hard to say pointer to member is not like a function. – apple apple Oct 16 '21 at 09:45
  • @appleapple You are right – Broothy Oct 16 '21 at 09:57
  • 2
    Maybe it would be simpler to just drop the requirement that all members inherit from the same base (or ignore it in this application). Now you need to create a heterogeneous compile-time list of pointers-to-members. You can store it in a tuple or in a variadic class template parameterised by actual pointer-to-member *values*. (This way you only need a *type* of your heterogeneous list, not its value). – n. m. could be an AI Oct 17 '21 at 07:02
  • @n.1.8e9-where's-my-sharem. Aaaa, what an idea using a tuple instead of an array, so simple, so clever. Thank you very much! – Broothy Oct 17 '21 at 09:25

1 Answers1

3

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.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • If I get it correctly you're comparing `T Base::x` and `T Derived::x`, while OP probably want `Base T::x` and `Derived T::x` – apple apple Oct 16 '21 at 08:05
  • 1
    @appleapple right you are, my bad. wrote a completely different answer. – n. m. could be an AI Oct 16 '21 at 09:03
  • @n.1.8e9-where's-my-sharem. thank you for the detailed answer. So there is no particular reason behind this 'restriction', it is what it is. "but there is no real need to use them polymorphically" - except my case, I tried to utilize it (obviously it was a failed attempt) :) – Broothy Oct 16 '21 at 09:31
  • @Broothy perhaps if you explain *why* you need to use pointers-to-members polymorphically, we could find a working solution to that problem. – n. m. could be an AI Oct 16 '21 at 09:41
  • @n.1.8e9-where's-my-sharem. The code is quite complicated, I try to create a simplified version and come back to you later. – Broothy Oct 16 '21 at 09:58
  • @n.1.8e9-where's-my-sharem. please check the update. In the linked code you will see why I miss the pointer-to-member polymorphism. – Broothy Oct 16 '21 at 15:19
  • @Broothy the code is an attempted *solution*, but what is the *problem*? Can you explain it in plain English? – n. m. could be an AI Oct 16 '21 at 19:48
  • @n.1.8e9-where's-my-sharem. I updated the question, sorry for the misunderstanding :) – Broothy Oct 17 '21 at 05:43