右值引用转发

rvalue reference forwarding

我正在围绕 std::jthread 和一些周边基础设施编写包装器。我无法理解为什么以下内容无法编译:

#include <iostream>
#include <map>
#include <functional>
#include <thread>

// two random functions
void foo(int i) { std::cout << "foo " << i << std::endl; }

void bar(int i) { std::cout << "bar " << i << std::endl; }

// mechanism to identify them
enum function_kind {
    foo_kind, bar_kind
};
std::map<function_kind, std::function<void(
        int)>> kind_to_function{{foo_kind, foo},
                                {bar_kind, bar}};

// wrapper around jthread
// (additional functionality ommitted for brevity)
template<typename Callable, typename... Args>
class MyThread {
public:
    explicit MyThread(Callable &&function, Args &&...args) : m_thread{
            std::forward<Callable>(function),
            std::forward<Args>(args)...} {}

private:
    std::jthread m_thread;
};

int main() {
    std::jthread t1(kind_to_function[foo_kind], 3); // works
    MyThread t2(kind_to_function[foo_kind], 3); // complains
    return 0;
}


我真的只是想模仿 std::jthread 用我自己的 class 做的任何事情。

IDE (clion) 抱怨说 t2 的第一个参数不是右值。编译器抱怨有点复杂:

main.cpp: In function ‘int main()’:
main.cpp:29:46: error: class template argument deduction failed:
   29 |     MyThread t2(kind_to_function[foo_kind], 3); // complains
      |                                              ^
main.cpp:29:46: error: no matching function for call to ‘MyThread(std::map<function_kind, std::function<void(int)> >::mapped_type&, int)’
main.cpp:20:14: note: candidate: ‘MyThread(Callable&&, Args&& ...)-> MyThread<Callable, Args> [with Callable = std::function<void(int)>; Args = {int}]’ (near match)
   20 |     explicit MyThread(Callable &&function, Args &&...args) : m_thread{std::forward<Callable>(function),
      |              ^~~~~~~~
main.cpp:20:14: note:   conversion of argument 1 would be ill-formed:
main.cpp:29:46: error: cannot bind rvalue reference of type ‘std::function<void(int)>&&’ to lvalue of type ‘std::map<function_kind, std::function<void(int)> >::mapped_type’ {aka ‘std::function<void(int)>’}
   29 |     MyThread t2(kind_to_function[foo_kind], 3); // complains
      |                                              ^
main.cpp:18:7: note: candidate: ‘template<class Callable, class ... Args> MyThread(MyThread<Callable, Args>)-> MyThread<Callable, Args>’
   18 | class MyThread {
      |       ^~~~~~~~
main.cpp:18:7: note:   template argument deduction/substitution failed:
main.cpp:29:46: note:   ‘std::function<void(int)>’ is not derived from ‘MyThread<Callable, Args>’
   29 |     MyThread t2(kind_to_function[foo_kind], 3); // complains
      |                                              ^

在任何情况下,参数都适用于 std::jthread,它也只接受右值...所以我错过了什么?

MyThread构造函数的参数不是转发引用,因为构造函数不是模板。不要使 class 成为模板,而只是构造函数:

class MyThread {
public:
    template<typename Callable, typename... Args>
    explicit MyThread(Callable &&function, Args &&...args) :
        m_thread{
            std::forward<Callable>(function),
            std::forward<Args>(args)...} {}

private:
    std::jthread m_thread;
};

In any case, the arguments work for std::jthread, which also just takes rvalues... So what am I missing?

jthread不是模板,它的构造函数是模板。这使得对模板参数的右值引用成为转发引用,而不是普通的右值引用。

但是,由于MyThread本身就是一个模板,而且它的构造函数不是模板构造函数,所以行为是不一样的。实例化后,它是一个只接受右值的常规构造函数。

转发引用取决于它们所属的函数模板发生的模板参数推导。所以 non-template 构造函数意味着没有转发引用。

好的,但是你没有给MyThread指定模板参数,为什么貌似没有错误?因为 class 模板参数推导允许您省略这些。 CTAD 发生在它自己的重载决策步骤中,与实际选择构造函数来初始化对象完全不同。一个步骤可以是 ill-formed 而另一个不是。