Purpose of Perfect Forwarding for Callable Argument in Invocation Expression

Purpose of perfect forwarding for Callable argument in invocation expression?

For the same purpose as for arguments: so when Func::operator() is a ref-qualified:

struct Functor
{
void operator ()() const & { std::cout << "lvalue functor\n"; }
void operator ()() const && { std::cout << "rvalue functor\n"; }
};

Demo

How do correctly use a callable passed through forwarding reference?

I'd say the general rule applies in this case. You're not supposed to do anything with a variable after it was moved/forwarded from, except maybe assigning to it.

Thus...

How do correctly use a callable passed through forwarding reference?

Only forward if you're sure it won't be called again (i.e. on last call, if at all).

If it's never called more than once, there is no reason to not forward.


As for why your snippet could be dangerous, consider following functor:

template <typename T>
struct foo
{
T value;
const T &operator()() const & {return value;}
T &&operator()() && {return std::move(value);}
};

As an optimization, operator() when called on an rvalue allows caller to move from value.

Now, your template wouldn't compile if given this functor (because, as T.C. said, std::function wouldn't be able to determine return type in this case).

But if we changed it a bit:

template <typename A, typename F>
auto bar (F && f)
{
std::vector<A> vect;

for ( auto i { 0u }; i < 10u ; ++i )
vect.push_back(std::forward<F>(f)());

return vect;
}

then it would break spectacularly when given this functor.

Perfect forwarding of a callable

Lambdas are anonymous structs with an operator(), the capture list is a fancy way of specifying the type of its members. Capturing by reference really is just what it sounds like: you have reference members. It isn't hard to see the reference dangles.

This is a case where you specifically don't want to perfectly forward: you have different semantics depending on whether the argument is a lvalue or rvalue reference.

template<class Callable>
auto discardable(Callable& callable)
{
return [&]() mutable { (void) callable(); };
}

template<class Callable>
auto discardable(Callable&& callable)
{
return [callable = std::forward<Callable>(callable)]() mutable { // move, don't copy
(void) std::move(callable)(); // If you want rvalue semantics
};
}

C++20 concept : requires expression and perfect forwarding

It will behave exactly like what it looks like. That's kind of the point of a requires expression: to make these things look like C++. So it will behave like C++.

What matters is how you use the concept. That is, when you requires some template based on it, you should invoke the concept correctly. For example:

template<typename Func, typename ...Args
void constrained(Func func, Args &&...args)
requires has_request_interface<Func, Args...>
{
Status status = func(std::forward<Args>(args)...);
}

So yes, if you want forwarding through the concept to work, your concept needs to use && and forwarding.

Perfect-forwarding a return value with auto&&

There are two deductions here. One from the return expression, and one from the std::invoke expression. Because decltype(auto) is deduced to be the declared type for unparenthesized id-expression, we can focus on the deduction from the std::invoke expression.

Quoted from [dcl.type.auto.deduct] paragraph 5:

If the placeholder is the decltype(auto) type-specifier, T shall be the placeholder alone. The type deduced for T is determined as described in [dcl.type.simple], as though e had been the operand of the decltype.

And quoted from [dcl.type.simple] paragraph 4:

For an expression e, the type denoted by decltype(e) is defined as follows:

  • if e is an unparenthesized id-expression naming a structured binding ([dcl.struct.bind]), decltype(e) is the referenced type as given in the specification of the structured binding declaration;

  • otherwise, if e is an unparenthesized id-expression or an unparenthesized class member access, decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;

  • otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;

  • otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;

  • otherwise, decltype(e) is the type of e.

Note decltype(e) is deduced to be T instead of T&& if e is a prvalue. This is the difference from auto&&.

So if std::invoke(std::forward<Callable>(op), std::forward<Args>(args)...) is a prvalue, for example, the return type of Callable is not a reference, i.e. returning by value, ret is deduced to be the same type instead of a reference, which perfectly forwards the semantic of returning by value.

Why or when should I cast callable function parameters to an rvalue before invocation?

The point of perfect forwarding is to preserve the original information as much as possible.

g(std::forward<Args>(args)...); will drop the rvalue/lvalue information of the original function object, g will always be treated as lvalue.

This will cause observable effect, for example:

struct foo {
void operator()(int) & {
std::cout << "& called\n";
}

void operator()(int) && {
std::cout << "&& called\n";
}
};

foo{}(1) will invoke the second operator(). If you use your first approach without std::forward, f(foo{}, 1) will invoke the first operator().



Related Topics



Leave a reply



Submit