12

我有一个函数模板,我想在其中完美地转发到我在另一个线程上运行的 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 以防万一这可能是编译器问题(可能不是)

4

1 回答 1

8

我理解它的方式你不能使用一个async期望非常量左值引用作为参数的函数,因为async总是会在内部复制它们(或将它们移动到内部)以确保它们存在并且在线程的整个运行时间内有效创建的。

具体来说,该标准说async(launch policy, F&& f, Args&&... args)

(§30.6.8)

(2) 要求:F并且每一项都应满足 MoveConstructible 的要求Ti。(20.8.2, 30.3.1.2) 应该是一个有效的表达式。ArgsINVOKE(DECAY_COPY (std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...)

(3) 效果:[...] 如果 policy & 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))...) 传播的任何异常都作为异常结果存储在共享状态中。
线程对象存储在共享状态中,并影响引用该状态的任何异步返回对象的行为。

不幸的是,这意味着您甚至不能用 a 替换引用std::reference_wrapper,因为后者不是可移动构造的。我想使用 astd::unique_ptr而不是引用会起作用(但是,这意味着您的函数参数将始终存在于堆上)。

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

如果您定义了一个将左值引用包装在 astd::reference_wrapper中但保持右值引用不变的函数,则可以在将参数传递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 函数时会自动转换回左值引用。

于 2012-12-11T05:48:07.467 回答