完美转发forward源码分析
# 为什么要用std forward
参考:https://blog.csdn.net/xiangbaohui/article/details/103673177 (opens new window) 这篇文章讲得非常好,建议读懂了再往下看。
关键点总结: 右值引用变量通过decltype看其类型是右值引用,但是变量本身是左值,当其作为参数进行函数重载时,会选择左值的重载。
# std forward源码
下面是std forward的源码
// Forward an lvalue.
template<typename _Tp>
constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }
// Forward an rvalue.
template<typename _Tp>
constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
下面把forward改成forward2,并针对上面参考文章中forward的典型用法写一个demo测试,分析forward的具体工作原理。
#include <iostream>
using namespace std;
// 打印一个类型的ref属性。 采用宏定义而不是函数,是为了防止函数传参导致类型变化
#define print_ref_type(tag, T) do{ \
printf("%s", tag); \
if(std::is_lvalue_reference<T>::value) printf(" & \n"); \
if(std::is_rvalue_reference<T>::value) printf(" && \n"); \
if(!std::is_reference<T>::value) printf(" NR \n"); \
}while(0)
// Forward an lvalue.
template<typename _Tp>
constexpr _Tp&& forward2(typename remove_reference<_Tp>::type& __t) noexcept
{
printf("forward2 & func entered.\n");
print_ref_type(" __t: ", decltype(__t));
print_ref_type(" _Tp: ", _Tp);
print_ref_type(" static_cast<_Tp&&>(__t): ", decltype(static_cast<_Tp&&>(__t)));
return static_cast<_Tp&&>(__t);
}
// Forward an rvalue.
template<typename _Tp>
constexpr _Tp&& forward2(typename remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue_reference type");
printf("type&& forward an rrrvalue.\n");
return static_cast<_Tp&&>(__t);
}
template<typename T>
void fun(T& val) {
cout << "fun::lvalue: " << val << endl;
}
template<typename T>
void fun(T&& val) {
cout << "fun::rvalue: " << val << endl;
}
template<typename T>
void test(T&& param) {
printf("test enter\n");
print_ref_type(" param: ", decltype(param));
print_ref_type(" T: ", T);
print_ref_type(" forward2<T>(param): ", decltype(forward2<T>(param)));
fun(forward2<T>(param)); //test函数中调用子函数fun时带上forward
}
int main() {
int a = 1;
test(a);
printf("\n");
test(2);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
编译运行,输出及解释如下:
$ g++ main.cpp
$ ./a.out
test enter # test函数的`T&& param`要匹配实际输入的int, 但是有通用引用符号&&存在,所以要匹配为int&或者int&&。又因为输入为左值,所以`T&& param`要匹配为int&
param: & # 故T只能为int&, 这样`T&& param`即为`int& && param`, 折叠为int&
T: &
forward2<T>(param): &
forward2 & func entered. # forward2有&和&&版本的重载,这里进入&版本的重载
__t: &
_Tp: & #这里_Tp就上面的T, 带引用
static_cast<_Tp&&>(__t): & # 返回值为(_Tp&&)类型,即`int& &&`, 折叠为int& ======关键在这里_Tp带了个&, 所以返回折叠为左值引用======
fun::lvalue: 1
test enter # test函数的`T&& param`要匹配实际输入的int&&
param: && # 所以T为int, 这样`T&& param`即为int&&
T: NR # 没有引用
forward2<T>(param): &&
forward2 & func entered. # forward2有&和&&版本的重载,这里进入&版本的重载,因为右值引用的变量本身是左值
__t: &
_Tp: NR #这里_Tp就上面的T, 没有引用
static_cast<_Tp&&>(__t): && ## 返回值为(_Tp&&)类型,即`int&&` ======关键在这里_Tp不带&, 所以直接返回int&&右值(注意不是右值引用)======
fun::rvalue: 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# forward的必要性
上面分析了forward的作用原理,但是没讲forward的必要性。如若test()函数中调用子函数fun时去掉forward,即:
// fun(forward2<T>(param)); //test函数中调用子函数fun时带上forward
fun(param); //test函数中调用子函数fun时去掉forward
1
2
2
那么程序输出如下:
$ ./a.out
test enter
param: &
T: &
forward2<T>(param): &
fun::lvalue: 1 # 直接进入fun的&版本,因为param本身就是&
test enter
param: &&
T: NR
forward2<T>(param): &&
fun::lvalue: 2 # 仍然进入fun的&版本,因为param变量是右值引用类型,右值引用类型的变量本身还是左值,重载时选择左值版本。
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
总结:
- 综上,如果没有forward完美转发,则右值第一次函数入参时为右值引用(本身是左值),函数内调用子函数入参时即为左值。
- 有了完美转发,子函数调用时加上forward,forward会将右值引用(本身是左值)强制类型转换为右值,子函数入参时仍为右值。
# decltype测试
decltype(右值) 得到的是普通类型,非引用 decltype(右值引用) 得到的是右值引用
int x=27;
int & a1 = x; // a1为int &
int && a2 = x+2; //a2为 int&&
printf("&a2=%p\n", &a2); //可以取地址
// int && a11 = a1; //编译出错 无法将右值引用绑定到左值, 即a1为左值
// int && a22 = a2; //编译出错 无法将右值引用绑定到左值, 即a2也为左值,
decltype(a2) a3 = x+3; //a3为 int&&, 即decltype(a2)为右值引用
decltype(a1+7) a4 = x+3; //a4为 int, 即decltype(右值)是表达式本身不带引用的类型
decltype(x) a5 = x; //a5为 int
decltype((x)) a6 = x; //a6为 int&
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
编辑 (opens new window)
上次更新: 2023/05/07, 17:27:54