模板参数推导
模板参数有三种写法:
|
|
根据《C++ Templates 2》的说法,第一种写法会decay
(等同于调用std::decay
),即:
- 所有的
const
,volatile
限定符会被移除 - 所有的引用(不管左值右值)会被移除
- 数组会退化为指向数组的指针,函数会退化为指向函数的指针
所以会有:
|
|
第二种写法会保留所有参数原本的类型,const
不会被丢弃,引用也会被保留。
但是有个问题:引用折叠,即当存在多个引用时,各个引用之间会发生折叠:
类型1 | 类型2 | 结果类型 |
---|---|---|
T | T& | T& |
T& | T& | T& |
T&& | T& | T& |
T&& | T&& | T&& |
也就是说,只有两个类型都是右值引用的时候,最后推导结果才会是左值引用。否则全部变为左值引用。
那么由于引用折叠的存在,FooWithRRef()
的参数类型一定是右值引用(因为左值会被折叠掉变为右值)。
那么要想做到传递左值,就必须使用FooWithLRef()
并且传入参数是左值引用类型,这样引用折叠才会折叠为左值引用。
完美转发
使用FooWithLRef(T&&)
仍旧有一个问题,就是在此函数内将参数传递给另一个函数时会有问题:
|
|
这里,如果传递左值FooWithLRef(std::move(a))
,那么T
会被推导为int
,那么value
的类型就是int&&
。这样传入的是左值引用。
但是左值引用本身是右值,所以在传入anotherFunc
的时候,仍旧是按照右值传递,这样会调用第一个函数,永远不会调用第二个函数。
当然,可以强制使用anotherFunc(std::move(a))
进行左值版本调用。那这样右值版本将永不调用。
这个时候就需要使用完美转发std::forward<T>(a)
,它会将左值引用再次变为左值,而右值引用仍旧是右值。
其他
关于FooWithLRef(T&&)
还有一个注意点,就是如果传入一个左值,如:
|
|
T
会被推导为int
,所以value
的类型是T&&->int&&
。
而如果传入右值或普通值:
|
|
那么T
会被推导为int&
,根据引用折叠,value
类型是int& && ->int&
。
而很离谱的是,如果你使用的是FooWithRRef(T&)
,传入普通类型并不会使T
变为int&
而是int
,最后value
会被推导为int&
。