mauve
mauve

Reputation: 2016

Template deduction fails when using std::result_of<F>

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

Answers (1)

Piotr Skotnicki
Piotr Skotnicki

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))();
}

DEMO


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

DEMO 2


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

DEMO 3

Upvotes: 5

Related Questions