Title
std::function and reference_closure do not use perfect forwarding
Status
resolved
Section
[func.wrap.func.inv]
Submitter
Alisdair Meredith

Created on 2008-03-16.00:00:00 last changed 162 months ago

Messages

Date: 2010-10-21.18:28:33

Proposed resolution:

Edit [func.wrap.func] paragraph 2:

2 A function object f of type F is Callable for argument types T1, T2, ..., TN in ArgTypes and a return type R, if, given lvalues t1, t2, ..., tN of types T1, T2, ..., TN, respectively, the expression INVOKE(f, declval<ArgTypes>()..., Rt1, t2, ..., tN), considered as an unevaluated operand ([expr]), is well formed (20.7.2) and, if R is not void, convertible to R.

Date: 2010-10-21.18:28:33

Rationale:

Addressed by 870.

Date: 2010-02-12.00:00:00

[ 2010-02-12 Moved to Tentatively NAD Editorial after 5 positive votes on c++std-lib. Rationale added below. ]

Date: 2010-02-11.00:00:00

[ 2010-02-11 This issue is now addressed by 870. ]

Date: 2010-02-10.00:00:00

[ 2010-02-10 Daniel opens to improve wording. ]

Date: 2010-02-09.00:00:00

[ 2010-02-09 Moved to Tentatively Ready after 5 positive votes on c++std-lib. ]

Date: 2010-02-09.00:00:00

[ 2010-02-09 Wording updated by Jonathan, Ganesh and Daniel. ]

Date: 2009-12-12.00:00:00

[ 2009-12-12 Howard adds: ]

"lvalues when Ti is an lvalue-reference and rvalues otherwise"

doesn't quite work here because the Ti aren't deduced. They are specified by the function type. Ti might be const int& (an lvalue reference) and a valid ti might be 2 (a non-const rvalue). I've taken another stab at the wording using "expressions" and "bindable to".

Date: 2009-12-12.00:00:00

[ 2009-12-12 Daniel adds: ]

I don't like the reduction to "values" and prefer the alternative solution suggested using "lvalues when Ti is an lvalue-reference and rvalues otherwise". The reason why I dislike the shorter version is based on different usages of "values" as part of defining the semantics of requirement tables via expressions. E.g. [utility.arg.requirements]/1 says "a, b, and c are values of type const T;" or similar in [container.requirements.general]/4 or /14 etc. My current reading of all these parts is that both rvalues and lvalues are required to be supported, but this interpretation would violate the intention of the suggested fix of #815, if I correctly understand Jonathan's rationale.

Date: 2009-12-12.00:00:00

[ 2009-12-12 Jonathan Wakely adds: ]

[func.wrap.func] says

2 A function object f of type F is Callable for argument types T1, T2, ..., TN in ArgTypes and a return type R, if, given lvalues t1, t2, ..., tN of types T1, T2, ..., TN, respectively, INVOKE (f, t1, t2, ..., tN) is well formed (20.7.2) and, if R is not void, convertible to R.

N.B. lvalues, which means you can't use function<R(T&&)> or function<R(unique_ptr<T>)>

I recently implemented rvalue arguments in GCC's std::function, all that was needed was to use std::forward<ArgTypes> in a few places. The example in issue 815 works.

I think 815 could be resolved by removing the requirement that the target function be callable with lvalues. Saying ArgTypes need to be CopyConstructible is wrong, and IMHO saying MoveConstructible is unnecessary, since the by-value signature implies that already, but if it is needed it should only be on operator(), not the whole class (you could in theory instantiate std::function<R(noncopyable)> as long as you don't invoke the call operator.)

I think defining invocation in terms of INVOKE already implies perfect forwarding, so we don't need to say explicitly that std::forward should be used (N.B. the types that are forwarded are those in ArgTypes, which can differ from the actual parameter types of the target function. The actual parameter types have gone via type erasure, but that's not a problem - IMHO forwarding the arguments as ArgTypes is the right thing to do anyway.)

Is it sufficient to simply replace "lvalues" with "values"? or do we need to say something like "lvalues when Ti is an lvalue-reference and rvalues otherwise"? I prefer the former, so I propose the following resolution for 815:

Edit [func.wrap.func] paragraph 2:

2 A function object f of type F is Callable for argument types T1, T2, ..., TN in ArgTypes and a return type R, if, given lvalues t1, t2, ..., tN of types T1, T2, ..., TN, respectively, INVOKE (f, t1, t2, ..., tN) is well formed (20.7.2) and, if R is not void, convertible to R.

Date: 2010-10-21.18:28:33

[ 2009-10 Santa Cruz: ]

Leave as open. Howard to provide wording. Howard welcomes any help.

Date: 2010-10-21.18:28:33

[ 2009-07 Frankfurt: ]

Leave this open and wait until concepts are removed from the Working Draft so that we know how to write the proposed resolution in terms of diffs to otherwise stable text.

Date: 2009-05-27.00:00:00

[ 2009-05-27 Daniel adds: ]

in the 2009-05-01 comment of above mentioned issue Howard

  1. Recommends to replace the CopyConstructible requirement by a MoveConstructible requirement
  2. Says: "Furthermore, the constraint need not be applied in function if I understand correctly. Rather the client must apply the proper constraints at the call site"

I'm fine with (a), but I think comment (b) is incorrect, at least in the sense I read these sentences. Let's look at Howard's example code:

function<R(ArgTypes...)>::operator()(ArgTypes... arg) const
{
   if (f_ == 0)
       throw bad_function_call();
   return (*f_)(std::forward<ArgTypes>(arg)...);
}

In the constrained scope of this operator() overload the expression "(*f_)(std::forward<ArgTypes>(arg)...)" must be valid. How can it do so, if ArgTypes aren't at least MoveConstructible?

Date: 2009-05-01.00:00:00

[ 2009-05-01 Howard adds: ]

Sebastian Gesemann brought to my attention that the CopyConstructible requirement on function's ArgTypes... is an unnecessary restriction.

template<Returnable R, CopyConstructible... ArgTypes>
class function<R(ArgTypes...)>
...

On further investigation, this complaint seemed to be the same issue as this one. I believe the reason CopyConstructible was put on ArgTypes in the first place was because of the nature of the invoke member:

template<class R, class ...ArgTypes>
R
function<R(ArgTypes...)>::operator()(ArgTypes... arg) const
{
    if (f_ == 0)
        throw bad_function_call();
    return (*f_)(arg...);
}

However now with rvalue-refs, "by value" no longer implies CopyConstructible (as Sebastian correctly points out). If rvalue arguments are supplied, MoveConstructible is sufficient. Furthermore, the constraint need not be applied in function if I understand correctly. Rather the client must apply the proper constraints at the call site. Therefore, at the very least, I recommend that CopyConstructible be removed from the template class function.

Furthermore we need to mandate that the invoker is coded as:

template<class R, class ...ArgTypes>
R
function<R(ArgTypes...)>::operator()(ArgTypes... arg) const
{
    if (f_ == 0)
        throw bad_function_call();
    return (*f_)(std::forward<ArgTypes>(arg)...);
}

Note that ArgTypes&& (the "perfect forwarding signature") is not appropriate here as this is not a deduced context for ArgTypes. Instead the client's arguments must implicitly convert to the non-deduced ArgType type. Catching these arguments by value makes sense to enable decay.

Next forward is used to move the ArgTypes as efficiently as possible, and also with minimum requirements (not CopyConstructible) to the type-erased functor. For object types, this will be a move. For reference type ArgTypes, this will be a copy. The end result must be that the following is a valid program:

#include <functional>
#include <memory>
#include <cassert>

std::unique_ptr<int>
f(std::unique_ptr<int> p, int& i)
{
    ++i;
    return std::move(p);
}

int main()
{
    int i = 2;
    std::function<std::unique_ptr<int>(std::unique_ptr<int>,
                                       int&> g(f);
    std::unique_ptr<int> p = g(std::unique_ptr<int>(new int(1)), i);
    assert(*p == 1);
    assert(i == 3);
}
Tested in pre-concepts rvalue-ref-enabled compiler.

In the example above, the first ArgType is unique_ptr<int> and the second ArgType is int&. Both must work!

Date: 2010-10-21.18:28:33

[ Sophia Antipolis: ]

According to Doug Gregor, as far as std::function is concerned, perfect forwarding can not be obtained because of type erasure. Not everyone agreed with this diagnosis of forwarding.

Date: 2008-03-16.00:00:00

std::function and reference_closure should use "perfect forwarding" as described in the rvalue core proposal.

History
Date User Action Args
2010-11-19 19:04:45adminsetstatus: nad editorial -> resolved
2010-10-21 18:28:33adminsetmessages: + msg3885
2010-10-21 18:28:33adminsetmessages: + msg3884
2010-10-21 18:28:33adminsetmessages: + msg3883
2010-10-21 18:28:33adminsetmessages: + msg3882
2010-10-21 18:28:33adminsetmessages: + msg3881
2010-10-21 18:28:33adminsetmessages: + msg3880
2010-10-21 18:28:33adminsetmessages: + msg3879
2010-10-21 18:28:33adminsetmessages: + msg3878
2010-10-21 18:28:33adminsetmessages: + msg3877
2010-10-21 18:28:33adminsetmessages: + msg3876
2010-10-21 18:28:33adminsetmessages: + msg3875
2010-10-21 18:28:33adminsetmessages: + msg3874
2010-10-21 18:28:33adminsetmessages: + msg3873
2010-10-21 18:28:33adminsetmessages: + msg3872
2010-10-21 18:28:33adminsetmessages: + msg3871
2008-03-16 00:00:00admincreate