Perfect Forwarding to async lambda

后端 未结 1 693
青春惊慌失措
青春惊慌失措 2021-02-20 06:11

I have a function template, where I want to do perfect forwarding into a lambda that I run on another thread. Here is a minimal test case which you can directly compile:

相关标签:
1条回答
  • 2021-02-20 06:31

    The way I understand it you cannot use a function through async that expects non-const lvalue references as arguments, because async will always make a copy of them internally (or move them inside) to ensure they exist and are valid throughout the running time of the thread created.

    Specifically, the Standard says about async(launch policy, F&& f, Args&&... args):

    (§30.6.8)

    (2) Requires: F and each Ti in Args shall satisfy the MoveConstructible requirements. INVOKE(DECAY_COPY (std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...) (20.8.2, 30.3.1.2) shall be a valid expression.

    (3) Effects: [...] if policy & launch::async is non-zero — calls INVOKE(DECAY_COPY (std::forward<F>(f)),DECAY_COPY (std::forward<Args>(args))...) (20.8.2, 30.3.1.2) as if in a new thread of execution represented by a thread object with the calls to DECAY_COPY() being evaluated in the thread that called async. Any return value is stored as the result in the shared state. Any exception propagated from the execution of INVOKE (DECAY_COPY (std::forward(f)), DECAY_COPY (std::forward(args))...) is stored as the exceptional result in the shared state.
    The thread object is stored in the shared state and affects the behavior of any asynchronous return objects that reference that state.

    Unfortunately, this means you cannot even replace the reference with a std::reference_wrapper, because the latter isn't move-constructible. I suppose using a std::unique_ptr instead of the reference would work (implying, however, that your function arguments will always live on the heap).

    (EDIT/CORRECTION)
    I was working on a related problem when I realized that std::reference_wrapper actually enables a workaround, although I claimed the opposite above.

    If you define a function that wraps lvalue references in a std::reference_wrapper, but leaves rvalue references unchanged, you can pass the T&& argument through this function before handing it over to std::async. I have called this special wrapper function wrap_lval below:

    #include <thread>
    #include <future>
    #include <utility>
    #include <iostream>
    #include <vector>
    #include <type_traits>
    
    /* First the two definitions of wrap_lval (one for rvalue references,
       the other for lvalue references). */
    
    template <typename T>
    constexpr T&&
    wrap_lval(typename std::remove_reference<T>::type &&obj) noexcept
    { return static_cast<T&&>(obj); }
    
    template <typename T>
    constexpr std::reference_wrapper<typename std::remove_reference<T>::type>
    wrap_lval(typename std::remove_reference<T>::type &obj) noexcept
    { return std::ref(obj); }
    
    
    /* The following is your code, except for one change. */
    template <typename T>
    std::string accessValueAsync(T&& obj)
    {
    
      std::future<std::string> fut =
        std::async(std::launch::async,
               [](T&& vec) mutable
               {
                 return vec[0];
               },
               wrap_lval<T>(std::forward<T>(obj)));   // <== Passing obj through wrap_lval
    
      return fut.get();
    }
    
    int main(int argc, char const *argv[])
    {
      std::vector<std::string> lvalue{"Testing"};
    
      std::cout << accessValueAsync(lvalue) << std::endl;
    
      std::cout << accessValueAsync(std::move(lvalue)) << std::endl;
    
      return 0;
    }
    

    With this change, both calls to accessValueAsync compile and work. The first one, which uses an lvalue reference, automatically wraps it in a std::reference_wrapper. The latter is automatically converted back to an lvalue reference when std::async calls the lambda function.

    0 讨论(0)
提交回复
热议问题