This page is a snapshot from the LWG issues list, see theLibrary Active Issues List for more information and the meaning ofResolved status.
std::function andreference_closure do not use perfect forwardingSection: 22.10.17.3.5[func.wrap.func.inv]Status:ResolvedSubmitter: Alisdair MeredithOpened: 2008-03-16Last modified: 2016-01-28
Priority:Not Prioritized
View all otherissues in [func.wrap.func.inv].
View all issues withResolved status.
Discussion:
std::function andreference_closure should use "perfect forwarding" asdescribed in the rvalue core proposal.
[Sophia Antipolis:]
According to Doug Gregor, as far as
std::functionis concerned, perfectforwarding can not be obtained because of type erasure. Not everyoneagreed with this diagnosis of forwarding.
[2009-05-01 Howard adds:]
Sebastian Gesemann brought to my attention that the
CopyConstructiblerequirement onfunction'sArgTypes...is an unnecessaryrestriction.template<Returnable R,CopyConstructible... ArgTypes>class function<R(ArgTypes...)>...On further investigation, this complaint seemed to be the sameissue as this one. I believe the reason
CopyConstructiblewas putonArgTypesin the first place was because of the nature of theinvoke member:template<class R, class ...ArgTypes>Rfunction<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,MoveConstructibleis sufficient. Furthermore, the constraint need not be applied infunctionif I understand correctly. Rather the client must apply the proper constraintsat the call site. Therefore, at the very least, I recommend thatCopyConstructiblebe removed from the template classfunction.Furthermore we need to mandate that theinvoker is coded as:
template<class R, class ...ArgTypes>Rfunction<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. Insteadthe client's arguments must implicitly convert to the non-deducedArgTypetype. Catching these arguments by value makes sense to enable decay.Next
forwardis used to move theArgTypesas efficiently aspossible, and also with minimum requirements (notCopyConstructible)to the type-erased functor. For object types, this will be amove. Forreference typeArgTypes, this will be a copy. The end resultmust bethat 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
ArgTypeisunique_ptr<int>and the secondArgTypeisint&. Bothmust work!
[2009-05-27 Daniel adds:]
in the 2009-05-01 comment of above mentioned issue Howard
- Recommends to replace the
CopyConstructiblerequirement by aMoveConstructiblerequirement- Says: "Furthermore, the constraint need not be applied in
functionif Iunderstand correctly. Rather the client must apply the proper constraintsat the call site"I'm fine with (a), but I think comment (b) is incorrect, at least in thesense 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 itdo so, ifArgTypesaren't at leastMoveConstructible?
[2009-07 Frankfurt:]
Leave this open and wait until concepts are removed from the WorkingDraft so that we know how to write the proposed resolution in terms ofdiffs 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
fof typeFis Callable for argumenttypesT1, T2, ..., TNinArgTypesand a return typeR, if, given lvaluest1, t2, ..., tNof typesT1, T2, ...,TN, respectively,INVOKE (f, t1, t2, ..., tN)is well formed(20.7.2) and, ifRis 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, allthat was needed was to usestd::forward<ArgTypes>in a fewplaces. The example in issue 815 works.I think 815 could be resolved by removing the requirement that the targetfunction be callable with lvalues. Saying
ArgTypesneed to beCopyConstructibleis wrong, and IMHO sayingMoveConstructibleis unnecessary, since the by-value signature implies that already, but if it isneeded it should only be onoperator(), not the whole class (you couldin theory instantiatestd::function<R(noncopyable)>as long asyou don't invoke the call operator.)I think defining invocation in terms of
INVOKEalready implies perfectforwarding, so we don't need to say explicitly thatstd::forwardshouldbe used (N.B. the types that are forwarded are those inArgTypes, whichcan differ from the actual parameter types of the target function. The actualparameter types have gone via type erasure, but that's not a problem - IMHOforwarding the arguments asArgTypesis the right thing to do anyway.)Is it sufficient to simply replace "lvalues" with "values"? or do we need to saysomething like "lvalues when
Tiis an lvalue-reference and rvaluesotherwise"? 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
fof typeFis Callable for argumenttypesT1, T2, ..., TNinArgTypesand a return typeR, if, givenlvaluest1, t2, ..., tNof typesT1, T2, ..., TN, respectively,INVOKE (f, t1, t2, ..., tN)iswell formed (20.7.2) and, ifRis notvoid, convertible toR.
[2009-12-12 Daniel adds:]
I don't like the reduction to "values" and prefer the alternative solutionsuggested 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 viaexpressions. E.g. 16.4.4.2[utility.arg.requirements]/1 says "
a,b, andcare values of typeconst T;" or similar in23.2.2[container.requirements.general]/4 or /14 etc. My current readingof all these parts is thatboth rvalues and lvalues are required to besupported, but this interpretation would violate the intention of the suggestedfix 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
Tiaren't deduced. They arespecified by thefunctiontype.Timight beconstint&(an lvalue reference) and a validtimight 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 by870(i).]
[2010-02-12 Moved to Tentatively NAD Editorial after 5 positive votes onc++std-lib. Rationale added below.]
Rationale:
Proposed resolution:
Edit 22.10.17.3[func.wrap.func] paragraph 2:
2 A function object
fof typeFis Callable for argumenttypesT1, T2, ..., TNinArgTypesandareturn typeR,if, given lvaluestheexpressiont1, t2, ...,tNof typesT1, T2, ..., TN, respectively,INVOKE(f,declval<ArgTypes>()...,R, considered as an unevaluatedoperand (7[expr]), is well formed (20.7.2)t1, t2, ..., tN)and, if.Ris notvoid, convertible toR