“转发”的含义不过是一个函数把自己的形参传递(转发)给另外一个函数而已。完美转发的含义是我们不仅转发对象,还转发其显著特征:类型,左值,右值,以及是否带有const
或volation
修饰词等。
假设有某函数f
,然后我们呢打算撰写一个函数將f
作为转发目标,考虑如下代码:
template<typename T>
void fwd(T && param) { //接受任意实参
f(std::forward<T>(param)); //转发到f
}
给定目标函数f
和转发函数fwd
,当以某特定实参f
会执行某操作,而用同一实参调用fwd
会执行不同操作,则称完美转发失败:
f(experssion); // 如果本语句执行某种操作
fwd(experssion);// 而本语句执行了不同的操作,则称fwd完美转发experssion到f失败
以下逐一讲解不能实施完美转发的实参
大括号初始化物
假设f
声明如下:
void f(const std::vector<int> &);
f({1, 2, 3}); // 没问题,{1, 2, 3}会隐式转换std::vector<int>
fwd({1, 2, 3}); // 错误! 无法通过编译
上述fwd
为大括号初始化物的运用,就是一种完美转发失败的情形;经由转发函数模板fwd
来对f
实施间接调用时,编译器就不再会比较fwd
调用处传入的实参所声明的形参数了。完美转发在如下条件成立时候失败
- 编译器无法为一个或多个
fwd
形惨推导出类型结果 - 编译器为一个或多个
fwd
的形参推导出了“错误的”类型结果
0和NULL用作空指针
条款8说过,若尝试把0和NULL以空指针传递给函数模板,类型推导就会发生行为扭曲,推导结果会是整型。结论就是:0和NULL
都不能用作空指针以进行完美转发
仅有声明的整型static const
成员变量
考虑如下代码:
class widget {
public:
static const std::size_t MinVals = 28; // 给出MinVals声明
};
void f(std::size_t val) {
cout << "this is f fuxntion";
}
template<typename T>
void fwd(T && param) { //接受任意实参
f(std::forward<T>(param)); //转发到f
}
std::vector<int> widgetData;
widgetData.reserve(widget::MinVals);
f(widget::MinVals); // 没有问题,当f(28)处理
fwd(widget::MinVals); // 错误,应该无法链接
重载的函数名字和模板名字
考虑如下代码:
int processval(int value) {
return 0;
}
int processval(int value, int priority) {
return 1;
}
f(processval); // 没有问题
上述f
调用中,编译器知道它们需要是调用的是哪个processVal
。但是fwd
就行不行了,因为作为一个函数模板,它没有任何关于类型需求的信息,这也使得编译器不可能决议应该传递哪个函数重载版本
fwd(processVal); // 错误!哪个processVal重载版本?
位域
void f(std::size_t sz) {
std::cout << "this is f function";
}
template<typename T>
void fwd(T &¶m) {
f(std::forward<T>(param));
}
IPv4Header h;
f(h.totalLength); // 没问题
fwd(h.totalLength); // 错误! 非const引用不得绑定到位域