完美转发到异步 lambda

2024-02-27

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


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

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

(§30.6.8)

(2) 要求:F和每个Ti in Args应满足 MoveConstructible 要求。INVOKE(DECAY_COPY (std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...)(20.8.2, 30.3.1.2) 应是有效的表达式。

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

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

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

如果您定义一个将左值引用包装在std::reference_wrapper,但保持右值引用不变,您可以传递T&&在将其传递给之前通过此函数进行参数std::async。我已经调用了这个特殊的包装函数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 函数。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

完美转发到异步 lambda 的相关文章

随机推荐