Andriy
Andriy

Reputation: 8604

Generic way to deduce the return type of a functor?

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

Upvotes: 15

Views: 4946

Answers (5)

Andriy
Andriy

Reputation: 8604

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)

Upvotes: 5

Anthony Williams
Anthony Williams

Reputation: 68701

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.

Upvotes: 11

pmr
pmr

Reputation: 59841

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

Upvotes: 14

Leonid Volnitsky
Leonid Volnitsky

Reputation: 9154

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.

Upvotes: 3

JohnB
JohnB

Reputation: 13743

A variant of (b) working not only with function pointers should be something like

template<typename Arg, typename Ret>
Ret ComputeSomething (Arg arg, function<auto (Arg) -> Ret> f)

Upvotes: 2

Related Questions