完美转发不仅仅是转发对象,而且还转发对象的修饰,无论是左值右值还是const或者volatile。但是完美转发也有失效的时候。
情况1:
template<typename T>
void fwd(T&& param)
{
f(std::forward<T>(param));
}
f(expression)
fwd(expression)
如果在上述模板函数中,f和fwd做的不是同一件事,那么fwd将无法将expression成功完美转发给f 。
假如f声明为:
void f(const std::vector<int>& v);
当调用时
f({1,2,3});
{1,2,3} 将会转换成 std::vector 格式。
但是当调用fwd 时:
fwd({1,2,3});
将会报错。原因是编译器会根据f的参数类型来进行推导,但是对于fwd的参数类型是不可知的。所以完美转发会在这些情况下失效:
1、编译器无法推导fwd参数的类型。
2、编译器推导出错误的fwd参数类型。这里的错误可能fwd的实例无法通过编译器推导的参数类型进行编译,也可能是fwd参数推导出来的类型与f接受的参数类型不一致。例如当f是重载函数的时候,推导出来的参数类型可能会导致f调用错误的函数。
解决这个问题可以用这个办法,利用auto的推导特性
auto il = {1,2,3};
fwd(il);
将{1,2,3} 推导为 std::initializer_list 类型,然后进行转发。
情况2:
用0或者NULL做空指针,解决这个很容易,就是用nullptr替代0或者NULL。
情况3:
只声明未定义的static const 和constexpr 成员。
一般情况下,当使用以上两种成员的时候,只声明,不定义,编译器会直接将其转换为整数值。不需要安排一块内存。
class Widget{
public:
static constexpr std::size_t MinVals = 28; //声明
};
std::vector<int> widgetData;
widgetData.reserve(Widget::MinVals);
void f(std::size_t val);//假设定义f如此
f(Widget::MinVals); //这么调用没问题,f接受28为参数
fwd(Widget::MinVals);// 在link阶段会报错
因为fwd 的参数是一个综合引用,引用一般可以看做指针,所以当传入参数的时候,需要参数地址,即参数被定义。要想解决这个问题,我们需要对成员进行一个定义:
constexpr std::size_t Widget::MinVals; // 在Widget的cpp文件中。
注意一点,这里就不用再进行初始化了,因为头文件中做过了,重复的话编译器会报错。
情况4:
重载和模板函数的名字,如下例子:
void f(int (*pf)(int));
或者
void f(int pf(int));
假设我们有两个重载函数:
int processVal(int value);
int processVal(int value, int priority);
当我们将重载函数传入给函数f的时候:
f(processVal);
这样做没问题,因为编译器知道应该将哪个函数传给f,根据此函数的接受的参数的限制。
但是:
fwd(processVal); // error! which processVal?
就会报错,因为作为模板函数,fwd并不知道应该将其推导为哪个函数。
同理:
template <typename T>
T workOnVal(T param)
{...}
fwd(workOnVal); // error! which workOnVal instantiation?
也会有类似的问题。
解决方案,将传入函数进行初始化,将其初始化为fwd函数所能接受的类型:
using ProcessFuncType = int (*)(int);
ProcessFuncType processValPtr = processVal;
fwd(processValPtr); //方法一
fwd(static_cast<ProcessFuncType>(workOnVal)); //方法二
情况5:
位域。
上例子:
struct IPv4Header {
std::uint32_t version:4,
IHL:4,
DSCP:6,
ECN:2,
totalLength:16;
...
};
void f(std::size_t sz); //被调用的函数
IPv4Header h;
...
f(h.totalLength); // fine
fwd(h.totalLength); // error!
fwd的参数是一个const 的引用,而h.totalLength是一个non-const的位域,c++的标准禁止将non-const的引用绑定在位域上。原因是 位域可能由一段计算机字节的随机部分组成(例如 一个32-bit int的3-5bits),但是没有办法去指向其地址。最小的能用指针指向的单位是 char 类型
如何解决呢,复制后传入函数:
auto length = static_cast<std::uint16_t>(h.totalLength);
fwd(length);
这一章有点难理解,尤其是英文的读物,并且最近确实有点懒,更新的有点拖沓,努力克服自己的懒惰,加油!