Reputation: 4752
Consider the code:
class Character
{
void kill();
void send_to_wall();
}
template <typename T>
void GeorgeFunc(T fp)
{
??? obj;
(obj.*fp)();
}
int main()
{
GeorgeFunc(&Character::kill);
}
So my question here is: how can I get ???
? It seems that the compiler definitely knows what this type is (Character
) during template instantiation, but I'm not sure how to get at it. My current workaround is to change to: void GeorgeFunc(void (T::*fp)())
, but it would be cleaner to simply get the type from the member function pointer. decltype(fp)
would return void(Character::*)()
, and decltype(fp())
would return void
. Any way to get Character
?
Upvotes: 19
Views: 3966
Reputation: 11
In C++17 one can use deduction guides to determine the class-type and const-ness without much boiler plate:
template<bool Const = false, typename R = void, typename C = void, typename... Args> struct MemberFunctionPointerInfoDetail
{
using ClassType = C;
using ReturnType = R;
using Arguments = std::tuple<Args...>;
static constexpr bool isConst = Const;
//explicit MemberFunctionPointerInfoDetail(auto){} //C++20 as noticed by duburcqa
template<typename T> explicit MemberFunctionPointerInfoDetail(T){}
};
template<typename R, typename C, typename... Args> MemberFunctionPointerInfoDetail(R(C::**)(Args...)) -> MemberFunctionPointerInfoDetail<false, R, C, Args...>;
template<typename R, typename C, typename... Args> MemberFunctionPointerInfoDetail(R(C::**)(Args...)const) -> MemberFunctionPointerInfoDetail<true, R, C, Args...>;
template<typename mfp> using MemberFunctionPointerInfo = decltype(MemberFunctionPointerInfoDetail((mfp*){}));
You can now use MemberFunctionPointerInfo<MFPType>::ClassType
as you wish.
Same with ResultType, Arguments (as tuple) and isConst.
The void-default arguments help to prevent compiler-errors if the deduction fails. Basically, MemberFunctionPointerInfo<int>
will not result in a compilation-error, but the ClassType becoming void. It is possible to static_assert this condition, if a descriptive compiler error is wanted.
Upvotes: 1
Reputation: 168988
Yes, just use a trait to determine this.
template <typename> struct member_function_traits;
template <typename Return, typename Object, typename... Args>
struct member_function_traits<Return (Object::*)(Args...)>
{
typedef Return return_type;
typedef Object instance_type;
typedef Object & instance_reference;
// Can mess with Args... if you need to, for example:
static constexpr size_t argument_count = sizeof...(Args);
};
// If you intend to support const member functions you need another specialization.
template <typename Return, typename Object, typename... Args>
struct member_function_traits<Return (Object::*)(Args...) const>
{
typedef Return return_type;
typedef Object instance_type;
typedef Object const & instance_reference;
// Can mess with Args... if you need to, for example:
static constexpr size_t argument_count = sizeof...(Args);
};
Now your declaration is:
typename member_function_traits<T>::instance_type obj;
However, I would argue that since you require a member function pointer (other types would fail to instantiate due to the line (obj.*fp)()
1) that your function should take a member function pointer directly instead of a completely generic type.
So this definition would not only work, but I would consider it preferred -- the error messages when someone uses something other than a pointer-to-member-function will be much clearer because the argument type will be incompatible:
template <typename Return, typename Object>
void GeorgeFunc(Return (Object::*fp)())
{
Object obj;
(obj.*fp)();
}
Note that this does allow passing a pointer-to-member-function that returns any type. Since we don't really use the return value, we don't care what it is. There is no reason to enforce that it is void
as in your "workaround."
The only downside to using this approach is that you need two overloads if you intend to also accept pointers to member functions that are declared const
. The completely generic implementation does not have this limitation. (I've long wished that pointers to const
member functions were implicitly convertible to pointers to non-const
member functions, but this is currently not allowed by C++.)
1 This isn't 100% true. If you use a completely generic type as you are right now then the caller could theoretically pass a member data pointer instead of a member function pointer. obj.*fp
would evaluate as a reference to the data member, and then you would be invoking operator()()
on it. As long as the data member's type implemented this operator then the template function GeorgeFunc
could be instantiated.
Upvotes: 22