完美转发到异步lambda [英] Perfect Forwarding to async lambda

查看:76
本文介绍了完美转发到异步lambda的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个函数模板,我想在其中完美地转发到我在另一个线程上运行的lambda中.这是一个可以直接编译的最小测试用例:

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:

#include <thread>
#include <future>
#include <utility>
#include <iostream>
#include <vector>

/**
 * Function template that does perfect forwarding to a lambda inside an
 * async call (or at least tries to). I want both instantiations of the
 * function to work (one for lvalue references T&, and rvalue reference T&&).
 * However, I cannot get the code to compile when calling it with an lvalue.
 * See main() below.
 */
template <typename T>
std::string accessValueAsync(T&& obj)
{

    std::future<std::string> fut =
        std::async(std::launch::async,
            [](T&& vec) mutable
            {
                return vec[0];
            },
            std::forward<T>(obj));

    return fut.get();
}

int main(int argc, char const *argv[])
{
    std::vector<std::string> lvalue{"Testing"};

    // calling with what I assume is an lvalue reference does NOT compile
    std::cout << accessValueAsync(lvalue) << std::endl;

    // calling with rvalue reference compiles
    std::cout << accessValueAsync(std::move(lvalue)) << std::endl;

    // I want both to compile.

    return 0;
}

对于非编译情况,这是可理解的错误消息的最后一行:

For the non-compiling case, here is the last line of the error message which is intelligible:

main.cpp|13 col 29| note:   no known conversion for argument 1 from ‘std::vector<std::basic_string<char> >’ to ‘std::vector<std::basic_string<char> >&’

我觉得这可能与推导出T&&的方式有关,但是我无法指出确切的故障点并进行修复.有什么建议吗?

I have a feeling it may have something to do with how T&& is deduced, but I can't pinpoint the exact point of failure and fix it. Any suggestions?

谢谢!

我正在使用gcc 4.7.0,以防万一这可能是编译器问题(可能不是)

I am using gcc 4.7.0 just in case this could be a compiler issue (probably not)

推荐答案

按照我的理解,您不能通过async使用期望非常量左值引用作为参数的函数,因为async总是会复制在内部(或将它们移入内部)以确保它们存在并在所创建线程的整个运行时间内有效.

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.

具体来说,该标准对async(launch policy, F&& f, Args&&... args)进行了说明:

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

(§30.6.8)

(§30.6.8)

(2)要求:F,并且Args中的每个Ti必须满足MoveConstructible要求. INVOKE(DECAY_COPY (std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...)(20.8.2,30.3.1.2)是有效的表达式.

(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)效果:[...]如果政策& launch :: async非零-调用INVOKE(DECAY_COPY (std::forward<F>(f)),DECAY_COPY (std::forward<Args>(args))...)(20.8.2,30.3.1.2),就好像在由线程对象表示的新执行线程中一样,调用async的线程中对DECAY_COPY()的调用进行了评估.任何返回值都将作为结果存储在共享状态中.从执行INVOKE(DECAY_COPY(std :: forward(f)),DECAY_COPY(std :: forward(args))...)传播的任何异常都作为异常结果存储在共享状态中.
线程对象以共享状态存储,并影响任何异步行为 返回引用该状态的对象.

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

不幸的是,这意味着您甚至不能将引用替换为std::reference_wrapper,因为后者不可移动构造.我想使用std::unique_ptr代替引用是可行的(但是,这意味着您的函数参数将始终存在于堆中).

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

(编辑/更正)
当我意识到std::reference_wrapper实际上可以解决问题时,我正在研究一个相关的问题,尽管我在上面提出了相反的看法.

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

如果定义一个将左值引用包装在std::reference_wrapper中的函数,但保留右值引用不变,则可以在将该函数传递给std::async之前将T&&参数传递给该函数.我在下面调用了这个特殊的包装函数wrap_lval:

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;
}

进行此更改后,对accessValueAsync的两个调用均会编译并运行.第一个使用左值引用,将其自动包装在std::reference_wrapper中.当std::async调用lambda函数时,后者会自动转换回左值引用.

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.

这篇关于完美转发到异步lambda的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆