给定目标函数 f
和转发函数 fwd
,当以某特定实参调用 f
会执行某操作,而用同一实参调用 fwd
会执行不同操作,则称完美转发失败:
f(expr);
fwd(expr);
有一些不能实施完美转发的实参:
假设 f
的声明如下:
void f(const std::vector<int>& v);
此时 f
可以通过编译,而 fwd
无法通过编译:
f({1, 2, 3}); // ok
fwd({1, 2, 3}); // error!
完美转发会在下面两个条件中的任何一个成立时失败:
- 编译器无法为一个或多个
fwd
的形参推导出类型结果。 - 编译器为一个或多个
fwd
形参推导出“错误的”类型结果。
上述 fwd
的调用中,问题在于未声明为 std::initializer_list
类型擦函数模板传递了大括号初始化器,这样的语境是 不推导语境,编译器会禁止在 fwd
的调用过程中从表达式 {1, 2, 3}
出发来推导类型。
有一个简单的解决方案:
auto il = {1, 2, 3};
fwd(il); // ok
对它们的推导结果是整型,而非指针类型,替换为 nullptr
可以解决这个问题。
考虑下面的代码:
class Widget {
public:
static const std;:size_t MinVals = 28;
...
};
...
std::vector<int> WidgetData;
widgetData.reserve(Widget::MinVals);
尽管 Widget::MinVals
并无定义,编译器绕过了它缺少定义的事实,将 28
塞到所有提到 MinVals
的地方。但是如果产生了对 MinVals
取址的需求,上面的代码虽然能通过编译,但是因为缺少定义,链接期会出错。
以 MinVals
调用 f
没问题,但是调用 fwd
会出现链接期错误:
f(Widget::MinVals); // ok
fwd(Widget::MinVals); // error!
标准规定,按引用传递 MinVals
时要求 MinVals
有定义。解决方案是添加定义:
const std::size_t Widget::MinVals; // Widget.cpp
假设 f
定义如下:
void f(int (*pf)(int));
假设有重载函数 processVal
:
int processVal(int value);
int processVal(int value, int priority);
processVal
可以传递给 f
, 却无法传递给 fwd
:
f(processVal); // ok
fwd(processVal); // error!
原因是 fwd
不知道该选择哪个重载版本,而 f
可以根据形参类型 processVal
来选择哪种重载版本。
同样的问题会出现在使用模板函数的场合:
template<typename T>
T workOnVal(T param)
{...}
fwd(workOnVal); // error!
解决方案是手动指定需要转发的那个重载版本或实例:
using ProcessFuncType =
int (*)(int);
ProcessFuncType processValPtr = processVal;
fwd(processValPtr); // ok
fwd(static_cast<ProcessFuncPtr>(workOnVal)); // ok
观察下面这个可以表示IPv4头部的模型:
struct Ipv4Header {
std::unit32_t version: 4,
IHL: 4,
DSCP: 6,
ECN: 2,
totalLength: 16;
...
};
f
接收 std::size_t
类型形参,然后用 IPv4Header
对象:
void f(std::size_t sz);
IPv4Header h;
...
f(h.totalLength); // ok
但是 fwd
无法转发:
fwd(h.totalLength);
C++标准规定:非 const
引用不得绑定到位域。可以传递位域的仅有按值传递和常量引用传递。在常量引用形参的情况下,标准要求此时引用实际绑定到存储在某种标准整型中的位域值的副本。常量引用不可能绑定到位域,它们绑定到的是“常规”对象,其中复制了位域的值。
你可以自己制作一个副本,并以该副本调用转发函数:
auto length = static_cast<std::unit16_t>(h.totalLength);
fwd(length);