我将添加一些有关@TartanLlama 答案的详细信息,说明为什么您的代码无法编译(即使没有明确的模板参数),以及为什么(在我看来)您的代码很危险。
在下文中,我将仅使用简单类型T
而不是您的参数包Args...
,因为它更易于解释并且不会改变含义。
关于转发参考的一点提醒......
首先,让我们举一个比你的例子更简单的例子:
template <typename T>
void f (T&&);
现在,让我们从各种来源进行实例化f
,假设具有以下变量:
std::string s;
const std::string cs;
...然后:
f(s); // instanciate f<std::string&>
f(cs); // instanciate f<const std::string&>
f(std::string()); // instanciate f<std::string&&>
您应该想知道:为什么第一个实例化f<std::string&>
而不是f<std::string>
?,但标准告诉你(§14.8.2.1#3 [temp.deduct.call]):
如果 P 是转发引用并且参数是左值,则使用类型“对 A 的左值引用”代替 A 进行类型推导。
回到我们最初的片段!
现在,让我们的例子复杂一点:
template <typename T>
struct A {};
template <typename T>
void f (A<T>, T&&);
还有一个实例:
std::string s;
A<std::string> as;
f(as, s);
以上等同于您的示例,并且将无法编译,但是为什么...?好吧,如上所述,当你有一个lvalue时,推导的类型T&&
是 is T&
, not T
,因此类型推导失败了,A<T>
因为编译器期望A<std::string&>
并且你给出了 a A<std::string>
。
所以现在我们知道我们必须做以下事情:
A<std::string&> ars;
A<std::string const&> acrs;
f(ars, s); // good
f(acrs, cs); // good
为什么危险?
好的,现在,这应该很好:
A<std::string&&> arrs;
f(arrs, std::string());
但它不是......因为 whenT
被推断为右值引用,T
is simple T
,所以编译器期待A<std::string>
。
所以问题来了:你要给一个方法一个右值,这个方法将把它转发给一个需要左值的函数。这没有错,但它可能不是你所期望的。
如何处理?
第一种可能性是强制第一个参数的类型,而不考虑推导的类型T
,例如:
template <typename T>
void f (A<typename std::remove_reference<T>::type>, T&&);
但请注意:
- 你将不得不添加更多的东西来处理
const
。
T&&
有人可能想知道第一个参数的类型何时固定(至少在您的情况下)的用处。
第二种可能性(警告:我不知道这是否是标准的!)是将第一个参数移到最后,然后从中推断出类型t
:
template <typename T>
void f (T &&t, A<decltype(std::forward<T>(t))>);
现在你在推导的类型T
和预期的类型之间有一个精确的匹配A
。
不幸的是,我不知道如何使用可变参数模板进行上述工作......