完美转发异步lambda

时间:2012-12-11 04:50:35

标签: c++ c++11 lambda perfect-forwarding stdasync

我有一个功能模板,我希望完美转发到我在另一个线程上运行的lambda。这是一个可以直接编译的最小测试用例:

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

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

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&&的推导方式有关,但我无法确定故障的确切位置并修复它。有什么建议吗?

谢谢!

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

1 个答案:

答案 0 :(得分:7)

我理解它的方式你不能通过async使用一个函数来预期非const左值引用作为参数,因为async将始终在内部复制它们(或将它们移到内部)确保它们存在并在创建的线程的整个运行时间内有效。

具体而言,标准说明了async(launch policy, F&& f, Args&&... args)

  

(§30.6.8)

     

(2)要求:FTi中的每个Args都应满足MoveConstructible要求。 INVOKE(DECAY_COPY (std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...)(20.8.2,30.3.1.2)应为有效表达。

     

(3)影响:[...]如果政策&amp; launch :: async非零 - 调用INVOKE(DECAY_COPY (std::forward<F>(f)),DECAY_COPY (std::forward<Args>(args))...)(20.8.2,30.3.1.2),好像在一个新的执行线程中,由一个线程对象表示,对DECAY_COPY()的调用在调用异步的线程。任何返回值都作为结果存储在共享状态中。从执行INVOKE(DECAY_COPY(std :: forward(f)),DECAY_COPY(std :: forward(args))...)传播的任何异常都作为异常结果存储在共享状态中。
  线程对象存储在共享状态中,并影响任何异步的行为   返回引用该状态的对象。

不幸的是,这意味着您甚至无法用std::reference_wrapper替换引用,因为后者不是可移动构造的。我想使用std::unique_ptr而不是引用会起作用(但是,暗示你的函数参数将永远存在于堆中)。

(适用EDIT /校正)
当我意识到std::reference_wrapper实际上启用了一种解决方法时,我正在研究一个相关的问题,尽管我声称上面的反面。

如果定义一个在std::reference_wrapper中包装左值引用的函数,但保持rvalue引用不变,则可以将T&&参数传递给此函数,然后再将其传递给std::async。我在下面调用了这个特殊的包装函数wrap_lval

#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函数时,后者会自动转换回左值引用。