Reputation: 2016
I am trying to make a function which calls and return the returned value of a function passed as a template parameter:
template <typename Function>
typename std::result_of<Function>::type
call_function(Function&& f)
{
return f();
}
template <typename Function, typename Class>
typename std::result_of<Function>::type
call_member_function(Function&& f, Class* instance)
{
return instance->*f();
}
//
// call site:
//
call_function(f);
call_member_function(&F::f, &instance);
Here is a ideone version: http://ideone.com/IYM10x (it fails in a similar way in VS2013.4)
I have substituted the argument to std::result_of
to different permutations of std::decay
, std::remove_reference
and std::remove_pointer
without any luck.
How do I make call_function
and call_member_function
compile, preferably also for functions which return void
?
Upvotes: 1
Views: 1001
Reputation: 48447
You don't need to apply a std::remove_reference
or a std::decay
transformation to a deduced Function
type so as to use it with std::result_of
. What you need is a proper syntax similar to a function call expression:
#include <type_traits>
#include <utility>
template <typename Function>
auto call_function(Function&& f)
-> typename std::result_of<Function()>::type
// ~^
{
return std::forward<Function>(f)();
}
template <typename Function, typename Class>
auto call_member_function(Function&& f, Class* instance)
-> typename std::result_of<Function(Class*)>::type
// ~~~~~~~^
{
return (instance->*std::forward<Function>(f))();
}
How do I make this work for functions of with different number of arguments?
#include <type_traits>
#include <utility>
template <typename Function, typename... Args>
auto call_function(Function&& f, Args&&... args)
-> typename std::result_of<Function(Args...)>::type
{
return std::forward<Function>(f)(std::forward<Args>(args)...);
}
template <typename Function, typename Class, typename... Args>
auto call_member_function(Function&& f, Class* instance, Args&&... args)
-> typename std::result_of<Function(Class*, Args...)>::type
{
return (instance->*std::forward<Function>(f))(std::forward<Args>(args)...);
}
The argument list (Args...) should already be part of Function, why do I need them again? Or rather is there a way to make std::result_of<> work without them?
Yes, a list of parameters is already a part of the deduced Function
signature. The trick is, this is not how std::result_of<F>
works - it only utilizes the syntax of a function declaration.
std::result_of<F>
is designed to query the result type of a given functor object when called with arguments of given types.
What normally would be the result type of a function, like int
for int(char,float)
, within std::result_of
it is treated as the type of a functor object that a function call operator will be applied to. Hence, when you have a Function
defined as follows:
using Function = int(char,float);
then the std::result_of<Function>::type
is equal to:
std::result_of<int(char,float)>::type
// | | |
// | | `~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.
// | `~~~~~~~~~~~~~~~~~~~~~~~~~. |
// `~~~~~~~~. | |
// V V V
decltype( std::declval<int>() ( std::declval<char>(), std::declval<float>() ) )
// ^ ^ ^~~~~~~~~~~~~~~^
// instance of a functor call operator arguments
That is, the result type deduced in a partial specialization of std::result_of
is used to obtain an instance of a functor object. Since there is no function call operator for int
defined, the above attempt fails to compile.
If the Function
is deduced as a reference to function, then you end up with an incomplete primary template of std::result_of
, because it doesn't even match any of its partial specializations.
If you want to get the return type of a function (without providing the arguments first, so decltype()
is not an option), you can deduce it during the function template argument deduction:
template <typename R, typename... Params, typename... Args>
R call_function(R(*f)(Params...), Args&&... args)
{
return f(std::forward<Args>(args)...);
}
template <typename R, typename Class, typename... Params, typename... Args>
R call_member_function(R(Class::*f)(Params...), Class* instance, Args&&... args)
{
return (instance->*f)(std::forward<Args>(args)...);
}
or by providing a separate trait class:
#include <utility>
#include <type_traits>
template <typename F>
struct return_type;
template <typename R, typename... Args>
struct return_type<R(Args...)> { using type = R; };
template <typename R, typename... Args>
struct return_type<R(*)(Args...)> { using type = R; };
template <typename R, typename C, typename... Args>
struct return_type<R(C::*)(Args...)> { using type = R; };
template <typename R, typename C, typename... Args>
struct return_type<R(C::*)(Args...) &> { using type = R; };
template <typename R, typename C, typename... Args>
struct return_type<R(C::*)(Args...) &&> { using type = R; };
template <typename R, typename C, typename... Args>
struct return_type<R(C::*)(Args...) const> { using type = R; };
template <typename R, typename C, typename... Args>
struct return_type<R(C::*)(Args...) const&> { using type = R; };
template <typename R, typename C, typename... Args>
struct return_type<R(C::*)(Args...) const&&> { using type = R; };
template <typename R, typename C, typename... Args>
struct return_type<R(C::*)(Args...) volatile> { using type = R; };
template <typename R, typename C, typename... Args>
struct return_type<R(C::*)(Args...) volatile&> { using type = R; };
template <typename R, typename C, typename... Args>
struct return_type<R(C::*)(Args...) volatile&&> { using type = R; };
template <typename R, typename C, typename... Args>
struct return_type<R(C::*)(Args...) const volatile> { using type = R; };
template <typename R, typename C, typename... Args>
struct return_type<R(C::*)(Args...) const volatile&> { using type = R; };
template <typename R, typename C, typename... Args>
struct return_type<R(C::*)(Args...) const volatile&&> { using type = R; };
template <typename Function, typename... Args>
auto call_function(Function&& f, Args&&... args)
-> typename return_type<typename std::remove_reference<Function>::type>::type
{
return std::forward<Function>(f)(std::forward<Args>(args)...);
}
template <typename Function, typename Class, typename... Args>
auto call_member_function(Function&& f, Class* instance, Args&&... args)
-> typename return_type<typename std::remove_reference<Function>::type>::type
{
return (instance->*std::forward<Function>(f))(std::forward<Args>(args)...);
}
Upvotes: 5