-2

To support some compile time magic I would like to use pointers to members like:

struct BaseT
{
};

struct DerivedT: public BaseT
{
};

struct TestT 
{
    DerivedT testMem;

    typedef BaseT (TestT::* TestTMemPtr);

    constexpr TestT() = default;

    static constexpr TestTMemPtr testMemOffset()
    {
        return &TestT::testMem;
    }
};

int main()
{
    constexpr TestT test;
}

I cannot return a pointer-to-derived-member as a pointer-to-base-member, I get this with clang:

cannot initialize return object of type 'TestT::TestTMemPtr' (aka 'BaseT (TestT::*)') with an rvalue of type 'DerivedT TestT::*'

I checked it with gcc:

error: invalid conversion from 'DerivedT TestT::*' to 'TestT::TestTMemPtr' {aka 'BaseT TestT::*'}

Is this the normal behavior? I thought I can always use a derived pointer as a base pointer.

UPDATE: Ok, the original example wasn't the best, I think this one is more expressive, so DerivedT* can be used as BaseT*, but DerivedT TestT::* cannot be used as BaseT TestT::*:

struct BaseT
{
};

struct DerivedT: public BaseT
{
};

struct TestT 
{
    DerivedT m_test;
};

using BaseTMemPtr = BaseT TestT::*;

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

1 Answers1

2

From the point of view of inheritance, BaseT TestT::* and DerivedT TestT::* are two unrelated types¹, so you can't initialize the former from the latter nor vice versa, just like you can't initialize a int* with a double* because int and double are not based and derived classes.


¹ By that I mean that two objects of these types don't point to two classes which are one the base of another. BaseT TestT::* and DerivedT TestT::* are both pointer types, but they don't point to two classes of which one is base of the other; they don't even point to classes in the first place (see demo code below), so there can be no inheritance relation between the pointed-to types, as inheritance is a thing between classes, not between types in general, such as member function types.

#include <type_traits>
struct BaseT {};

struct DerivedT: public BaseT {};

struct TestT {};

template<typename T, typename = void>
struct points_to_class : std::false_type {};
template<typename T>
struct points_to_class<T*> : std::is_class<T> {};

static_assert(points_to_class<BaseT*>::value); // passes
static_assert(points_to_class<BaseT TestT::*>::value); // fails

But so, is the conversion between pointers only possible when they both point to classes and those two classes are related by inheritance?

Well, if you give a look at the Pointer declaration page on cppreference.com, it does have a section on Pointers to member functions, and it is about conversion between pointers to member functions.

But it is about of a pointer to member function of a base class to pointer to the same member function of a derived class, whereas you seem to look for converting a pointer to member function (of TestT) returning a base class (BaseT) to a pointer to member function of the same class (TestT) returning a derived class (DerivedT). Again, the two types are unrelated.

Enlico
  • 23,259
  • 6
  • 48
  • 102
  • Please check the updated example. `int * i; double * d{i};` it is wrong obviously, but `DerivedT* x; BaseT* y{x};` is a better example and it works. – Broothy Oct 15 '21 at 17:21
  • @Broothy, `BaseT` is the base of `DerivedT`, but `BaseT TestT::*` is not the base of `DerivedT TestT::*`. – Enlico Oct 15 '21 at 18:12
  • Maybe it is my misconception, but what is &TestT::m_test? A member pointer of type DerivedT. If I can use a DerivedT pointer as a BaseT pointer then what's wrong with using a DerivedT member pointer as a BaseT member pointer? For me the analogy is obvious. – Broothy Oct 15 '21 at 18:43
  • `std::is_base_of_v` - of course it is false as BaseT pointer is not base of DerivedT pointer, like `static_assert(std::is_base_of_v)` fails as well. `static_assert(std::is_base_of_v);` - it pass as BaseT (not pointer) is base of DerivedT (not pointer). This is why we can use DerivedT* (pointers) as BaseT*, but I don't understand why it is not the true for pointer-to-member DerivedT TestT::* and BaseT TestT::* – Broothy Oct 15 '21 at 18:51
  • @Broothy, they are two distinct types not related by inheritance in any way (read the first sentence in your last comment). Why should we be able to point to one with the poiter to the other? How would that be different from expecting that we can point to a `double` via a `int*`? – Enlico Oct 15 '21 at 18:54
  • BaseT and DerivedT are related. The pointer types are not related, neither (BaseT* - DerivedT*) nor (BaseT TestT::* - DerivedT TestT::*). BUT a DerivedT pointer is also a BaseT pointer. If we follow this analogy a DerivedT member pointer should have been also a BaseT member pointer. – Broothy Oct 15 '21 at 18:59
  • 1
    @Broothy, I've simplified the question because it was definitely more complicated that it needs to be. What do you think now? – Enlico Oct 15 '21 at 19:17
  • "they don't even point to classes in the first place" - but they are :) DerivedT TestT::* is a (member pointer) to DerivedT. So instead of pointing a DerivedT object in the whole memory it points a DerivedT object relative to the containing TestT object. – Broothy Oct 15 '21 at 19:17
  • "but they are". That's wrong. I'll put a simple demonstration. – Enlico Oct 15 '21 at 19:25
  • I see your point, they are not classical pointers, but they point an object in the memory which obviously has a type. This is why I thought - as a Derived pointer is a Base pointer - a Derived member pointer is a Base member pointer. I do not see the logic behind handling normal pointers and member pointers differently in the language level, but it is how it is. I would like to wait a bit if somebody has a better or more detailed explanation. Thank you! – Broothy Oct 15 '21 at 19:45
  • You can init a `Base*` with a `Derived*` because a `Derived` _is a_ `Base`. If you could initialize a `Base X::*` with a `Derived X::*` that would mean that a member function of `X` returning `Base` _is a_ member function of `X` returning `Derived`. I don't see how this can seem reasonable. – Enlico Oct 15 '21 at 19:49
  • 1
    Functions and objects are handled differently all across the standard. The whole function footprint is part of the function's 'type' and there is no relation like a DerivedT* is a BaseT*. On member pointers (not on member function pointers) the standard could allow the same without any consequence. But now I may understand the pointer to member concept: it does not point to an object (DerivedT*) inside an object, but points to a part of a TestT object (which member has a DerivedT type BTW). So the polymorphism cannot work as the pointer does not point a DerivedT* thing just a part of TestT obj – Broothy Oct 16 '21 at 06:43
  • @Broothy, it took some time for you to accept my answer and, at the same time, I had to understand a few things to in the process of improving my answer. Therefore, if you think it still needs some improvement (_you should highlight this and that_, _you can mention that bla_, ...) please, let me know, so it can be useful to future readers as well :) – Enlico Oct 18 '21 at 07:52
  • I asked a more general question in this topic: [link](https://stackoverflow.com/questions/69593587). As I see it could have been implemented in a different way, but it is what it is. So you are right, we do not know how pointers-to-members implemented, so one should not make assumptions about polymorphism. It could be possible, but C++ uses a different approach. – Broothy Oct 18 '21 at 08:09