无法通过 std::ref() 调用带有 auto& 参数的 std::invoke()

Posted

技术标签:

【中文标题】无法通过 std::ref() 调用带有 auto& 参数的 std::invoke()【英文标题】:Cannot call std::invoke() with auto& parameter via std::ref() 【发布时间】:2018-02-23 13:29:52 【问题描述】:

我正在尝试创建一个Invoker 对象,它同时存储一个函子和该函子的一组参数 - 全部按值(用于线程)。 Invoker::operator()() 将使用复制的参数调用存储的仿函数。

到目前为止一切正常,直到有人尝试使用std::ref(variable) 通过auto& 传递参数。具体来说,这段代码应该可以工作,但它不会使用给定的错误消息进行编译:

int var = 0;
Invoker
    [](auto& r) 
        printf("%d\n", r);
    , std::ref(var)
();

我希望它的工作方式类似于 std::thread 在此示例中的工作方式。 错误信息是:

test.cpp:65:14: error: no matching function for call to ‘invoke(std::__tuple_element_t<0, std::tuple<main()::<lambda(auto:1&)>, std::reference_wrapper<int> > >, std::__tuple_element_t<1, std::tuple<main()::<lambda(auto:1&)>, std::reference_wrapper<int> > >)’
   std::invoke(std::get<Indicies>(std::move(args))...);
   ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

我现在的Invoker班级:

template<typename... Args>
struct Invoker 
    std::tuple<std::decay_t<Args>...> args;

    explicit Invoker(Args... args)
        : args(std::forward<Args>(args)...)
     

    template<size_t... Indices>
    void _Invoke(std::index_sequence<Indices...>) 
        std::invoke(std::get<Indices>(std::move(args))...);
    

    void operator()() 
        _Invoke(std::make_index_sequence<std::tuple_size_v<decltype(args)>>);
    
;

/* Invoker deduction guide (perfectly forward any parameters with full type!) */
template<typename Function, typename... Args>
Invoker(Function&&, Args&&...) -> Invoker<Function&&, Args&&...>;

See here 获取此问题的在线版本。错误消息表明,auto&amp; 的推导类型是 std::reference_wrapper&lt;int&gt;&amp;,而它应该是 int&amp;。不幸的是,我无法想出解决这个问题的办法。

编辑:

如 cmets 所示,表达式

int var = 5;
std::thread [](auto& r)  printf("%d\n", r); , std::ref(var) ;

仅使用 gcc &gt;= 7.1.0 编译。我很高兴看到有关此主题的详细说明,特别是如果这是 c++ 标准的正确行为。

【问题讨论】:

auto 的推导使用相同的模板参数推导规则,这不允许隐式转换。可以使用int n = 5; auto&amp; r = ref(n); 重现该问题。由于ref(n) 是一个右值,它不能绑定到左值引用。 @0x499602D2 尽管如此,当使用std::thread 时它可以正常工作。我想复制这种行为 @nyronium 不,std::thread([] (auto &amp;ref) , std::ref(var)); 从不编译。 @liliscent 你错了。见proof。 报告为gcc.gnu.org/bugzilla/show_bug.cgi?id=84532 【参考方案1】:

INVOKE 通常不会打开 reference_wrapper 参数;它们按原样使用(这里有一个不相关的例外:如果您调用指向成员的指针,并以 reference_wrapper 作为第一个参数,则该参数被解包)。因此,invoke([](auto&amp;), std::ref(var)); 将尝试使用右值 reference_wrapper 调用 lambda,因为尝试将右值绑定到左值引用是错误的。

观察到的std::thread 行为是已修复的libstdc++ bug。简而言之,libstdc++ 的std::thread 将提供的参数存储在tuple 中,该make_tuple (错误地)构造了make_tuple(它解开了reference_wrappers)。

【讨论】:

以上是关于无法通过 std::ref() 调用带有 auto& 参数的 std::invoke()的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 std::ref 不能按预期工作?

std::ref用法以及和&引用区别

std::atomic_ref 如何为非原子类型实现?

将 std::ref() 与 nvcc 一起使用时,类没有成员“second_argument_type”[重复]

c++11 std::ref std::cref

第7章 按值传递或按引用传递:7.3 使用std::ref()和std::cref()