Search code examples
c++c++11metaprogrammingcompile-time

"Derived pointer to member" to "base pointer to member" error


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
}

Solution

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