2021. Further incorrect usages of result_of

Section: 23.14.11.3 [func.bind.bind], 33.6.1 [futures.overview], 33.6.9 [futures.async] Status: C++14 Submitter: Daniel Krügler Opened: 2010-12-07 Last modified: 2016-02-10

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:

  1. According to 23.14.11.3 [func.bind.bind] p. 3:

    [..] The effect of g(u1, u2, ..., uM) shall be INVOKE(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.

  2. Similar in 23.14.11.3 [func.bind.bind] p. 10 bullet 2 we have:

    if the value of is_bind_expression<TiD>::value is true, the argument is tid(std::forward<Uj>(uj)...) and its type Vi is result_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:

    "[..] its type 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)

  3. 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 33.6.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 33.6.9 [futures.async] p. 3:

    Invocation of the deferred function evaluates INVOKE(g, xyz) where g is the stored value of decay_copy(std::forward<F>(f)) and xyz is the stored copy of decay_copy(std::forward<Args>(args))....

    This seems to imply that lvalues are provided in contrast to the direct call expression of 33.6.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.

If 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.

  1. Change 23.14.11.3 [func.bind.bind] p. 3 as indicated:

    template<class F, class... BoundArgs>
      unspecified bind(F&& f, BoundArgs&&... bound_args);
    

    -2- Requires: is_constructible<FD, F>::value shall be true. For each Ti in BoundArgs, is_constructible<TiD, Ti>::value shall be true. INVOKE(fd, w1, w2, ..., wN) (20.8.2) shall be a valid expression for some values w1, w2, ..., wN, where N == sizeof...(bound_args).

    -3- Returns: A forwarding call wrapper g with a weak result type (20.8.2). The effect of g(u1, u2, ..., uM) shall be INVOKE(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 of g and the values and types of the bound arguments v1, v2, ..., vN are determined as specified below. […]

  2. Change 23.14.11.3 [func.bind.bind] p. 7 as indicated:

    template<class R, class F, class... BoundArgs>
       unspecified bind(F&& f, BoundArgs&&... bound_args);
    

    -6- Requires: is_constructible<FD, F>::value shall be true. For each Ti in BoundArgs, is_constructible<TiD, Ti>::value shall be true. INVOKE(fd, w1, w2, ..., wN) shall be a valid expression for some values w1, w2, ..., wN, where N == sizeof...(bound_args).

    -7- Returns: A forwarding call wrapper g with a nested type result_type defined as a synonym for R. The effect of g(u1, u2, ..., uM) shall be INVOKE(fd, std::forward<V1>(v1), std::forward<V2>(v2), ..., std::forward<VN>(vN), R), where the values and types of the bound arguments v1, v2, ..., vN are determined as specified below. […]

  3. Change 23.14.11.3 [func.bind.bind] p. 10 as indicated:

    -10- The values of the bound arguments v1, v2, ..., vN and their corresponding types V1, V2, ..., VN depend on the types TiD derived from the call to bind and the cv-qualifiers cv of the call wrapper g as follows:

    • if TiD is reference_wrapper<T>, the argument is tid.get() and its type Vi is T&;
    • if the value of is_bind_expression<TiD>::value is true, the argument is tid(std::forward<Uj>(uj)...) and its type Vi is result_of<TiD cv & (Uj&&...)>::type&&;
    • if the value j of is_placeholder<TiD>::value is not zero, the argument is std::forward<Uj>(uj) and its type Vi is Uj&&;
    • otherwise, the value is tid and its type Vi is TiD cv &.
  4. This resolution assumes that the wording of 33.6.9 [futures.async] is intended to provide rvalues as arguments of INVOKE.

    Change the function signatures in header <future> synopsis 33.6.1 [futures.overview] p. 1 and in 33.6.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);
    
  5. Change 33.6.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 — Stores DECAY_COPY(std::forward<F>(f)) and DECAY_COPY(std::forward<Args>(args))... in the shared state. These copies of f and args constitute a deferred function. Invocation of the deferred function evaluates INVOKE(std::move(g), std::move(xyz)) where g is the stored value of DECAY_COPY(std::forward<F>(f)) and xyz is the stored copy of DECAY_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 of INVOKE(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 a policy value of launch::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 to async.