This question is a follow-up of How to deduce the type of the functor's return value? I'm reformulating it in a more abstract way.
Given the pseudocode of a template function
template <typename Arg, typename Fn>
auto ComputeSomething(Arg arg, Fn fn) -> decltype(<decl-expr>)
{
// do something
// ............
return fn(<ret-expr>)
}
where <ret-expr>
is an arbitrary expression which involves arg
, what shall I use for <decl-expr>
to set the return type of ComputeSomething
equal to the return type of the functor.
The functor may be a class, a lambda or a function pointer.
Partial solutions I found so far.
(a) The answer for my linked question done by ecatmur. Essentially, it is repeating the return statement in <decl-expr>
. Problems: it is error-prone and wouldn't work if contains local variables.
(b) It works only for function pointers
template <typename Arg, typename Ret>
Ret ComputeSomething(Arg arg, Ret(*fn)(Arg))
(c) It assumes that the argument of the functor is of type Arg
(which may not hold in general) and requires Arg
to be default-constructible
template <typename Arg, typename Fn>
auto ComputeSomething(Arg arg, Fn fn) -> decltype(fn(Arg())
(d) Using std::declval
which is supposed to lift the default-constructible restriction, as suggested in how to deduce the return type of a function in template. Could anybody explain how it works?
template <typename Arg, typename Fn>
auto ComputeSomething(Arg arg, Fn fn) -> decltype(fn(std::declval<Arg>())
Use result_of. It is backwards compatible and takes all the ugly declval
pain out of your code. You still need to remember to add rvalue reference qualifiers (&&
) if you actually just forward values.
Something else I find important: Your function forwards arguments to another function. In such cases you should always use rvalue references to pass the arguments.
If all you are trying to do is improve maintainability: there are several attempts at a RETURNS
macro around that try to minimize the repetition between the return type declaration and the actual return expression, but I haven't seen any that allows a function body that contains more than the actual return statement.
As for how declval
works: Its compiler dependent. It isn't allowed to occur in an evaluated content and its argument can be an incomplete type. See 20.2.4
std::declval
is a function template that is only declared (not defined). It can thus only be used in unevaluated contexts such as the argument to sizeof
and decltype
. It is declared to return an rvalue of the specified type. This allows you to use it to manufacture a dummy parameter for a function call in a decltype
expression.
e.g.
typedef decltype(fn(std::declval<Arg>())) t;
declares t
to be the type of the result of calling fn
with an rvalue of type Arg
. This is similar to your case (c) (fn(Arg())
), but it doesn't require anything of Arg
, so it works on types without default constructors.
If your return expression uses a local variable of type foo
, then you can use decltype(fn(std::declval<foo>()))
, again regardless of how you construct a foo
.
If you need an lvalue, such as a named object or an lvalue reference, then you can use std::declval<foo&>()
. This allows you to handle the case where the type depends on whether you have an lvalue or an rvalue.
Here's my own solution, the best I could get
template <typename Arg, typename Fn>
typename std::result_of<Fn(Arg)>::type ComputeSomething(Arg arg, Fn fn)
To make (c) works for anything, you need 2 overloads. 1st as shown in (c), 2nd:
template <typename Arg, typename Ret>
Ret ComputeSomething(Arg arg, std::function<Ret(Arg)> fn)
Also, as gcc bug 54111 shows - deduction of return type is very unreliable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With