#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?
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:
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.)