Search code examples
c++pointerspointer-to-member

How to decompose a pointer-to-member in C++ (get class and member types)?


I have this scenario:

#include <iostream>

class SomeClass
{
public:
    int _int;
};

#define DO_SOME_STUFF(ptr) std::cout << /* Print the typeid().hash_code() of the type which ptr is poiting to (int) */;

int main()
{
    int SomeClass::* ptr_to_int_member = &SomeClass::_int;
    DO_SOME_STUFF(ptr_to_int_member)
}

I want to know which type is ptr pointing at (which is currently int). Knowing which class owns that int is also useful (which is currently SomeClass).


Solution

  • You can do that with a "template trick":

    template<typename T>
    struct PointerToMemberDecomposer {};
    
    template<typename T, typename P>
    struct PointerToMemberDecomposer<P T::*>
    {
        using ClassType = T;
        using MemberType = P;
    };
    

    And change your code to:

    #include <iostream>
    
    template<typename T>
    struct PointerToMemberDecomposer {};
    
    template<typename T, typename P>
    struct PointerToMemberDecomposer<P T::*>
    {
        using ClassType = T;
        using MemberType = P;
    };
    
    class SomeClass
    {
    public:
        int _int;
    };
    
    #define DO_SOME_STUFF(ptr) std::cout << typeid(PointerToMemberDecomposer<decltype(ptr)>::MemberType).hash_code();
    
    int main()
    {
        int SomeClass::* ptr_to_int_member = &SomeClass::_int;
        DO_SOME_STUFF(ptr_to_int_member)
    }
    

    Defining a couple of templated aliases can make the code a little bit cleaner:

    #define GET_POINTER_TO_MEMBER_CLASS_TYPE(ptr) PointerToMemberDecomposer<decltype(ptr)>::ClassType
    #define GET_POINTER_TO_MEMBER_MEMBER_TYPE(ptr) PointerToMemberDecomposer<decltype(ptr)>::MemberType
    

    So you can change DO_SOME_STUFF to:

    #define DO_SOME_STUFF(ptr) std::cout << typeid(GET_POINTER_TO_MEMBER_MEMBER_TYPE(ptr)).hash_code();
    

    Explanation

    This technique is called Partial template specialization. The second definition of PointerToMemberDecomposer will be used when a pointer-to-member type is passed as template argument; And will catch new T and P typenames. using those new typenames; It will define two type aliases (ClassType and MemberType) so T and P can be used outside of the PointerToMemberDecomposer struct.

    When using PointerToMemberDecomposer; you should use decltype operator which acts like type in Python or typeof in C#. decltype(x) passes the type of x instead of x itself.


    Update

    As 463035818_is_not_a_number have mentioned; macros can be replaced with templated aliases

    template <typename T>
    using ClassTypeFromPtrToMember_t = typename PointerToMemberDecomposer<T>::ClassType;
    
    template <typename T>
    using MemberTypeFromPtrToMember_t = typename PointerToMemberDecomposer<T>::MemberType;
    

    But you should still use decltype while DO_SOME_STUFF is a macro instead of a templated function and we cant access ptr's type directly (see 463035818_is_not_a_number's answer for templated function version of DO_SOME_STUFF):

    #define DO_SOME_STUFF(ptr) std::cout << typeid(MemberTypeFromPtrToMember_t<decltype(ptr)>).hash_code();
    

    In this case; DO_SOME_STUFF can be converted to a templated function. But you might want to for example fill a non capturing lambda with macro arguments; which requires DO_SOME_STUFF to be a macro.

    Also, you might want to change ClassType and MemberType to type and create two separated structs (or classes) for retrieving those type aliases; If you want PointerToMemberDecomposer to look like C++'s standard library.

    For more details; see 463035818_is_not_a_number's answer