std::function
and reference_closure
do not use perfect forwardingSection: 22.10.17.3.5 [func.wrap.func.inv] Status: Resolved Submitter: Alisdair Meredith Opened: 2008-03-16 Last modified: 2016-01-28
Priority: Not Prioritized
View all other issues in [func.wrap.func.inv].
View all issues with Resolved status.
Discussion:
std::function
and reference_closure
should use "perfect forwarding" as
described in the rvalue core proposal.
[ 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.
[ 2009-05-01 Howard adds: ]
Sebastian Gesemann brought to my attention that the
CopyConstructible
requirement onfunction
'sArgTypes...
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 onArgTypes
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 infunction
if I understand correctly. Rather the client must apply the proper constraints at the call site. Therefore, at the very least, I recommend thatCopyConstructible
be removed from the template classfunction
.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 forArgTypes
. Instead the client's arguments must implicitly convert to the non-deducedArgType
type. Catching these arguments by value makes sense to enable decay.Next
forward
is used to move theArgTypes
as efficiently as possible, and also with minimum requirements (notCopyConstructible
) to the type-erased functor. For object types, this will be amove
. For reference typeArgTypes
, 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
isunique_ptr<int>
and the secondArgType
isint&
. Both must work!
[ 2009-05-27 Daniel adds: ]
in the 2009-05-01 comment of above mentioned issue Howard
- Recommends to replace the
CopyConstructible
requirement by aMoveConstructible
requirement- 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, ifArgTypes
aren't at leastMoveConstructible
?
[ 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.
[ 2009-10 Santa Cruz: ]
Leave as open. Howard to provide wording. Howard welcomes any help.
[ 2009-12-12 Jonathan Wakely adds: ]
22.10.17.3 [func.wrap.func] says
2 A function object
f
of typeF
is Callable for argument typesT1, T2, ..., TN
inArgTypes
and a return typeR
, if, given lvaluest1, t2, ..., tN
of typesT1, T2, ..., TN
, respectively,INVOKE (f, t1, t2, ..., tN)
is well formed (20.7.2) and, ifR
is notvoid
, convertible toR
.N.B. lvalues, which means you can't use
function<R(T&&)>
orfunction<R(unique_ptr<T>)>
I recently implemented rvalue arguments in GCC's
std::function
, all that was needed was to usestd::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 beCopyConstructible
is wrong, and IMHO sayingMoveConstructible
is unnecessary, since the by-value signature implies that already, but if it is needed it should only be onoperator()
, not the whole class (you could in theory instantiatestd::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 thatstd::forward
should be used (N.B. the types that are forwarded are those inArgTypes
, 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 asArgTypes
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 22.10.17.3 [func.wrap.func] paragraph 2:
2 A function object
f
of typeF
is Callable for argument typesT1, T2, ..., TN
inArgTypes
and a return typeR
, if, givenlvaluest1, t2, ..., tN
of typesT1, T2, ..., TN
, respectively,INVOKE (f, t1, t2, ..., tN)
is well formed (20.7.2) and, ifR
is notvoid
, convertible toR
.
[ 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. 16.4.4.2 [utility.arg.requirements]/1 says "
a
,b
, andc
are values of typeconst T;
" or similar in 23.2.2 [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.
[ 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 thefunction
type.Ti
might beconst int&
(an lvalue reference) and a validti
might be2
(a non-const rvalue). I've taken another stab at the wording using "expressions" and "bindable to".
[ 2010-02-09 Wording updated by Jonathan, Ganesh and Daniel. ]
[ 2010-02-09 Moved to Tentatively Ready after 5 positive votes on c++std-lib. ]
[ 2010-02-10 Daniel opens to improve wording. ]
[ 2010-02-11 This issue is now addressed by 870. ]
[ 2010-02-12 Moved to Tentatively NAD Editorial after 5 positive votes on c++std-lib. Rationale added below. ]
Rationale:
Addressed by 870.
Proposed resolution:
Edit 22.10.17.3 [func.wrap.func] paragraph 2:
2 A function object
f
of typeF
is Callable for argument typesT1, T2, ..., TN
inArgTypes
andareturn typeR
,if, given lvaluesthe expressiont1, t2, ..., tN
of typesT1, T2, ..., TN
, respectively,INVOKE(f, declval<ArgTypes>()..., R
, considered as an unevaluated operand (7 [expr]), is well formed (20.7.2)t1, t2, ..., tN)and, if.R
is notvoid
, convertible toR