Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::result_of simple function

#include <iostream>
#include <type_traits>

double f(int i)
{
        return i+0.1;
}

struct F
{
        public:
        double operator ()(int i) { return i+0.1; }
};

int
main(int, char**)
{
        std::result_of<F(int)>::type x;     // ok
        // std::result_of<f(int)>::type x; // error: template argument 1 is invalid
        x = 0.1;
        std::cerr << x << std::endl;
}

Please explain why std::result_of<f(int)>::type x; is invalid...

cppreference says "(std::result_of) Deduces the return type of a function call expression at compile type.".

what's the problem?

like image 424
user1494506 Avatar asked Jul 13 '12 12:07

user1494506


1 Answers

std::result_of<T> requires T to be a type --but not just any type. T must be a function type so this partial specialization of result_of will be used:

template <class Fn, class... ArgTypes> struct result_of<Fn(ArgTypes...)>;

such that:

decltype(INVOKE(declval<Fn>(), declval<ArgTypes>()...))

is well-formed (C++11 20.9.7.6). (INVOKE is defined in 20.8.2.)

The reason std::result_of<f(int)> does not work is because f is not a type --it is an instance of a function type. To declare x to be the return type of f applied to an int, simply write this:

decltype(f(int{})) x;

or if you prefer hard-coding an int:

decltype(f(32)) x;

If the type of f is desired then use:

using FuncPtr = decltype(f);

In the provided code F (i.e., not lower case f) however is a type and so F(int) defines a the type representing the function that returns F accepting an int as an argument. Clearly this is not what F is! F's type is a struct whose instances can use the function call operator. F also has no explicit or implicit constructors taking an int, etc. as well. How can this work? Short answer: Template "magic".

Essentially, the definition of std::result_of takes the type, F(int) and separates the return type from the argument types so it can determine which case of INVOKE() would allow it to work. The cases of INVOKE are:

  1. F is a pointer to a member function for some class T
  2. If there is only one argument, F is a pointer to a data member of class T, or,
  3. An instance of F can be used as a function, i.e.,
declval<F>()(declval<int>())

which can be a normal function call or some type of functor (e.g., like your example).

Once this has been determined result_of can then determine the return type of the valid expression. This is what is returned via result_of's type member.

The beautiful thing about this is that the user of result_of need not know anything about how this actually works. The only thing one has to understand is that result_of needs a function TYPE. If one is using names that are not types within code (e.g., f) then decltype will need to be used to obtain the type of an expression with such.

Finally, part of the reason why f cannot be considered as a type is because template parameters also allow constant values and f is a constant function pointer value. This is easily demonstrated (using the question's definition of f):

template <double Op(int)>
double invoke_op(int i)
{
  return Op(i);
}

and later:

std::cout << invoke_op<f>(10) << std::endl;

So to obtain the return value type of an expression properly invoking f with some int one would write:

decltype(f(int{}))

(Note: f is never called: the compiler simply uses the expression within decltype to determine its result i.e., its return value in this instance.)

like image 110
Paul Preney Avatar answered Nov 04 '22 01:11

Paul Preney