result_of
Section: 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>::value
is true, the argument istid(std::forward<Uj>(uj)...)
and its typeVi
isresult_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)
whereg
is the stored value ofdecay_copy(std::forward<F>(f))
andxyz
is 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>::value
shall be true. For eachTi
inBoundArgs
,is_constructible<TiD, Ti>::value
shall be true.INVOKE(fd, w1, w2, ..., wN)
(20.8.2) shall be a valid expression for some valuesw1
,w2
, ...,wN
, whereN == sizeof...(bound_args)
.g
with 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 ofg
and the values and types of the bound argumentsv1
,v2
, ...,vN
are 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>::value
shall be true. For eachTi
inBoundArgs
,is_constructible<TiD, Ti>::value
shall be true.INVOKE(fd, w1, w2, ..., wN)
shall be a valid expression for some valuesw1
,w2
, ...,wN
, whereN == sizeof...(bound_args)
.g
with a nested typeresult_type
defined 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
, ...,vN
are 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
, ...,vN
and their corresponding typesV1
,V2
, ...,VN
depend on the typesTiD
derived from the call to bind and the cv-qualifiers cv of the call wrapperg
as follows:
- if
TiD
isreference_wrapper<T>
, the argument istid.get()
and its typeVi
isT&
;- if the value of
is_bind_expression<TiD>::value
istrue
, the argument istid(std::forward<Uj>(uj)...)
and its typeVi
isresult_of<TiD cv & (Uj&&...)>::type&&
;- if the value
j
ofis_placeholder<TiD>::value
is not zero, the argument isstd::forward<Uj>(uj)
and its typeVi
isUj&&
;- otherwise, the value is
tid
and its typeVi
isTiD 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::deferred
is non-zero — StoresDECAY_COPY(std::forward<F>(f))
andDECAY_COPY(std::forward<Args>(args))...
in the shared state. These copies off
andargs
constitute a deferred function. Invocation of the deferred function evaluatesINVOKE(std::move(g), std::move(xyz))
whereg
is the stored value ofDECAY_COPY(std::forward<F>(f))
andxyz
is 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 apolicy
value 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
.