2

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

OverShifted
  • 457
  • 1
  • 7
  • 17

2 Answers2

5

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

OverShifted
  • 457
  • 1
  • 7
  • 17
  • 2
    Pretty nice answer until the *"macros to make it cleaner"*. No. If you want to make it cleaner you might use a type alias. – spectras Jul 05 '21 at 08:14
  • 1
    fwiw, it is common to name the memebr alias just `type`. You could have two seperate traits, `Member::type` and `Class::type`. – 463035818_is_not_an_ai Jul 05 '21 at 08:14
  • agree with spectras, nice answer, only the macros are a little off. For readability you can `using type = PointerToMemberDecomposer::Class`. Your macro isnt much less to type than the original and has the downsides of being a macro (https://stackoverflow.com/questions/14041453/why-are-preprocessor-macros-evil-and-what-are-the-alternatives). – 463035818_is_not_an_ai Jul 05 '21 at 08:17
  • minotr nitpick: "It declares the type of the expression.." isnt quite right. Rather ["Inspects the declared type of an entity or the type and value category of an expression. "](https://en.cppreference.com/w/cpp/language/decltype). – 463035818_is_not_an_ai Jul 05 '21 at 08:19
  • 1
  • I liked the answer much better before the Update. I don't understand what you mean with "you might want to for example fill a non capturing lambda with macro arguments;". Nowadays necessary uses of macros are extremely rare. Since C++20 introduced `std::source_location` I am actually not aware of any valid use case, unless we are talking about large scale code generation. – 463035818_is_not_an_ai Jul 05 '21 at 20:08
  • also it doesnt fit the question-answer dialoge well, the update is a reply to my answer rather than to your question. Also, frankly, I think you didnt quite get the point of replacing the macro. The template alias is merely to avoid the necessity of `typename` when using the trait, it is not essential to replace the macro – 463035818_is_not_an_ai Jul 05 '21 at 20:11
  • note that it is completely fine to have more than one complementary answers. Not everything must be included in a single answer. – 463035818_is_not_an_ai Jul 05 '21 at 20:14
  • @463035818_is_not_a_number In the real code; I use a macro to create a property-getter lambda. this macro takes in a `pointer-to-member` and puts it into the lambda function. If I use a template function; the lambda should capture that `pointer-to-member` and it could no longer be stored as a `function-pointer`. If you have a solution for that (such as taking the `pointer-to-member` as another template); please help. because I am also not a fan of macros. – OverShifted Jul 07 '21 at 04:51
  • @OverShifted if you post it as question I am sure we can find a solution without macros. – 463035818_is_not_an_ai Jul 07 '21 at 08:13
2

Just summarizing comments to some otherwise great answer...

Member aliases are commonly named just type. Macros are better avoided (Why are preprocessor macros evil and what are the alternatives?) and for less verbosity on the caller you can use a function template:

#include <iostream>
#include <typeinfo>

template<typename T>
struct TypeFromPtrToMember; // needs no definition

template<typename T, typename P>
struct TypeFromPtrToMember<P T::*>
{
    using type = T;
};

class SomeClass
{
public:
    int _int;
};

template <typename T>
void do_some_stuff(T t){
    std::cout << typeid(typename TypeFromPtrToMember<T>::type).hash_code();;
}

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

Naming the member alias type is so common that I would do it even if you then need two traits. The other trait is basically the same just with using type = P;.

In the above, there is still the little annoyance of having to write typename when using the trait (because TypeFromPtrToMember<T>::type is a dependent name). Since C++11 we can use a template alias to help with that. Template aliases cannot be partially specialized, but we already have the trait and just need to forward to that:

template <typename T>
using TypeFromPtrToMember_t = typename TypeFromPtrToMember<T>::type;

Such that do_some_stuff can be:

template <typename T>
void do_some_stuff(T t){
    std::cout << typeid(TypeFromPtrToMember_t<T>).hash_code();;
}

I hope you agree that now no macros are needed anymore.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185