result_ofSection: 22.10.15.4 [func.bind.bind], 32.10.1 [futures.overview], 32.10.9 [futures.async] Status: C++14 Submitter: Daniel Krügler Opened: 2010-12-07 Last modified: 2016-01-28
Priority: Not Prioritized
View all other issues in [func.bind.bind].
View all issues with C++14 status.
Discussion:
Issue 2017 points out some incorrect usages of result_of in the
declaration of the function call operator overload of reference_wrapper,
but there are more such specification defects:
[..] The effect of
g(u1, u2, ..., uM)shall beINVOKE(fd, v1, v2, ..., vN, result_of<FD cv (V1, V2, ..., VN)>::type)[..]
but fd is defined as "an lvalue of type FD constructed from std::forward<F>(f)". This means that
the above usage must refer to result_of<FD cv & (V1, V2, ..., VN)> instead.
Similar in 22.10.15.4 [func.bind.bind] p. 10 bullet 2 we have:
if the value of
is_bind_expression<TiD>::valueis true, the argument istid(std::forward<Uj>(uj)...)and its typeViisresult_of<TiD cv (Uj...)>::type
Again, tid is defined as "lvalue of type TiD constructed from std::forward<Ti>(ti)". This means that
the above usage must refer to result_of<TiD cv & (Uj...)> instead. We also have similar defect as in
2017 in regard to the argument types, this leads us to the further corrected form
result_of<TiD cv & (Uj&&...)>. This is not the end: Since the Vi
are similar sensitive to the argument problem, the last part must say:
Vi is result_of<TiD cv & (Uj&&...)>::type &&"
(The bound arguments Vi can never be void types, therefore we don't need
to use the more defensive std::add_rvalue_reference type trait)
The function template async is declared as follows (the other overload has the same problem):
template <class F, class... Args> future<typename result_of<F(Args...)>::type> async(F&& f, Args&&... args);
This usage has the some same problems as we have found in reference_wrapper (2017) and more: According to
the specification in 32.10.9 [futures.async] the effective result type is that of the call of
INVOKE(decay_copy(std::forward<F>(f)), decay_copy(std::forward<Args>(args))...)
First, decay_copy potentially modifies the effective types to decay<F>::type and decay<Args>::type....
Second, the current specification is not really clear, what the value category of callable type or the arguments shall be: According
to the second bullet of 32.10.9 [futures.async] p. 3:
Invocation of the deferred function evaluates
INVOKE(g, xyz)wheregis the stored value ofdecay_copy(std::forward<F>(f))andxyzis the stored copy ofdecay_copy(std::forward<Args>(args))....
This seems to imply that lvalues are provided in contrast to the direct call expression of 32.10.9 [futures.async] p. 2 which implies rvalues instead. The specification needs to be clarified.
[2011-06-13: Daniel comments and refines the proposed wording changes]
The feedback obtained following message c++std-lib-30745 and follow-ups point to the intention, that
the implied provision of lvalues due to named variables in async should be provided as rvalues to support
move-only types, but the functor type should be forwarded as lvalue in bind.
bind were newly invented, the value strategy could be improved, because now we have a preference of
ref & qualified function call operator overloads. But such a change seems to be too late now.
User-code that needs to bind a callable object with an ref && qualified function call
operator (or conversion function to function pointer) needs to use a corresponding wrapper similar to reference_wrapper
that forwards the reference as rvalue-reference instead.
The wording has been adapted to honor these observations and to fit to FDIS numbering as well.
[Bloomington, 2011]
Move to Ready
Proposed resolution:
The suggested wording changes are against the FDIS.
Change 22.10.15.4 [func.bind.bind] p. 3 as indicated:
template<class F, class... BoundArgs> unspecified bind(F&& f, BoundArgs&&... bound_args);-2- Requires:
-3- Returns: A forwarding call wrapperis_constructible<FD, F>::valueshall be true. For eachTiinBoundArgs,is_constructible<TiD, Ti>::valueshall be true.INVOKE(fd, w1, w2, ..., wN)(20.8.2) shall be a valid expression for some valuesw1,w2, ...,wN, whereN == sizeof...(bound_args).gwith a weak result type (20.8.2). The effect ofg(u1, u2, ..., uM)shall beINVOKE(fd, std::forward<V1>(v1), std::forward<V2>(v2), ..., std::forward<VN>(vN), result_of<FD cv & (V1, V2, ..., VN)>::type), where cv represents the cv-qualifiers ofgand the values and types of the bound argumentsv1,v2, ...,vNare determined as specified below. […]
Change 22.10.15.4 [func.bind.bind] p. 7 as indicated:
template<class R, class F, class... BoundArgs> unspecified bind(F&& f, BoundArgs&&... bound_args);-6- Requires:
-7- Returns: A forwarding call wrapperis_constructible<FD, F>::valueshall be true. For eachTiinBoundArgs,is_constructible<TiD, Ti>::valueshall be true.INVOKE(fd, w1, w2, ..., wN)shall be a valid expression for some valuesw1,w2, ...,wN, whereN == sizeof...(bound_args).gwith a nested typeresult_typedefined as a synonym forR. The effect ofg(u1, u2, ..., uM)shall beINVOKE(fd, std::forward<V1>(v1), std::forward<V2>(v2), ..., std::forward<VN>(vN), R), where the values and types of the bound argumentsv1,v2, ...,vNare determined as specified below. […]
Change 22.10.15.4 [func.bind.bind] p. 10 as indicated:
-10- The values of the bound arguments
v1,v2, ...,vNand their corresponding typesV1,V2, ...,VNdepend on the typesTiDderived from the call to bind and the cv-qualifiers cv of the call wrappergas follows:
- if
TiDisreference_wrapper<T>, the argument istid.get()and its typeViisT&;- if the value of
is_bind_expression<TiD>::valueistrue, the argument istid(std::forward<Uj>(uj)...)and its typeViisresult_of<TiD cv & (Uj&&...)>::type&&;- if the value
jofis_placeholder<TiD>::valueis not zero, the argument isstd::forward<Uj>(uj)and its typeViisUj&&;- otherwise, the value is
tidand its typeViisTiD cv &.
This resolution assumes that the wording of 32.10.9 [futures.async] is intended to provide rvalues
as arguments of INVOKE.
Change the function signatures in header <future> synopsis 32.10.1 [futures.overview] p. 1
and in 32.10.9 [futures.async] p. 1 as indicated:
template <class F, class... Args> future<typename result_of<typename decay<F>::type(typename decay<Args>::type...)>::type> async(F&& f, Args&&... args); template <class F, class... Args> future<typename result_of<typename decay<F>::type(typename decay<Args>::type...)>::type> async(launch policy, F&& f, Args&&... args);
Change 32.10.9 [futures.async] as indicated: (Remark: There is also a tiny editorial correction
in p. 4 that completes one :: scope specifier)
-3- Effects: […]
- […]
- if
policy & launch::deferredis non-zero — StoresDECAY_COPY(std::forward<F>(f))andDECAY_COPY(std::forward<Args>(args))...in the shared state. These copies offandargsconstitute a deferred function. Invocation of the deferred function evaluatesINVOKE(std::move(g), std::move(xyz))wheregis the stored value ofDECAY_COPY(std::forward<F>(f))andxyzis the stored copy ofDECAY_COPY(std::forward<Args>(args)).... The shared state is not made ready until the function has completed. The first call to a non-timed waiting function (30.6.4) on an asynchronous return object referring to this shared state shall invoke the deferred function in the thread that called the waiting function. Once evaluation ofINVOKE(std::move(g), std::move(xyz))begins, the function is no longer considered deferred. [ Note: If this policy is specified together with other policies, such as when using apolicyvalue oflaunch::async | launch::deferred, implementations should defer invocation or the selection of the policy when no more concurrency can be effectively exploited. — end note ]
-4- Returns: an object of type
future<typename result_of<typename decay<F>::type(typename decay<Args>::type...)>::type>that refers to the associated asynchronous state created by this call toasync.