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
).
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();
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
typename
s. using those new typename
s; 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.
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 struct
s (or class
es) 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