右值引用转发
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 而另一个不是。
我正在围绕 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 而另一个不是。