当左值引用参数和右值引用参数作为相同的转发引用类型传递时,为什么它不起作用?

Why does it not work when an lvalue-ref-arg and an rvalue-ref-arg are passed as the same forwarding-ref type?

#include <type_traits>

template<typename T>
void f(T&& a, T&& b)
{}

int main()
{
    int n;
    f(n, std::move(n));
}

T&&是转发引用类型,所以我觉得decltype(a)应该是int&decltype(b)应该是int&&.

但是,上面的代码会产生以下错误:

main.cpp(13,2): error : no matching function for call to 'f' f(n, std::move(n));

main.cpp(7,6): note: candidate template ignored: deduced conflicting types for parameter 'T' ('int &' vs. 'int')

void f(T&& a, T&& b)

1 error generated.

为什么左值引用参数和右值引用参数作为相同的转发引用类型传递时不起作用?

我的编译器是 clang 4.0。

编译器错误非常直接,T 不能同时在 int&int 中推导出来。为了让它工作,你必须提供一个额外的模板参数:

template<typename T1, typename T2>
void f(T1&& a, T2&& b)
{}

推理

模板参数推导在处理 (a.k.a Universal references) which are based on reference collapsing rules

时遵循特殊规则
  • U& & 变成 U&
  • U& && 变成 U&
  • U&& & 变成 U&
  • U&& && 变成 U&&

例如,如果您有函数模板:

template<typename T>
void foo(T&&) {}
  1. 如果foo输入是U类型的左值,T将被减去U&。遵循上面的引用折叠规则,参数类型将变为 U&.
  2. 如果 foo 输入是 U 类型的右值,T 将减去 U,因此参数类型将为 U&&

现在按照上面的推理,模板参数推导会将 T 推导为第一个参数的 int&,因为输入是 lvalue

模板参数推导将尝试匹配第二个参数的类型,但对于第二个参数,因为输入是遵循上述规则的右值 T 将被推导为 int

此时编译器举起双手尖叫“伙计T的推导类型必须匹配所有输入参数”。

在使用转发引用(1) 进行非表面深度工作时,重要的是要了解它们的实际工作原理,而不是将它们视为 "magic."

转发引用的所有"magic"都是标准中的这个规定:

当参数类型 T && 发生类型推导时,其中 T 是推导类型,并且类型 U 的左值用作参数,类型 U & 用于类型推导而不是 U.

举个例子:

template <class T>
void foo(T&& a);

int i = 42;

调用 foo(42) 时会发生什么:42int 类型的右值。因此,T 将被推断为 int,因此 a 的类型将是 int &&,对 int.

的右值引用

调用 foo(i) 时会发生什么:iint 类型的左值。由于转发引用规则,推导将发生,就好像 int & 是类型一样。 T 因此推导为 int &;因此 a 的类型是“int & &&”,它的引用折叠为 int &.

考虑到这一点,您的示例失败的原因就很清楚了。必须将相同的 T 推导为 int &(因为 n)和 int(因为 std::move(n))。自然推演失败

如果您希望每个参数都单独作为转发引用,则每个参数都需要一个单独的模板参数:

template<class T, class U>
void f(T&& a, U&& b)
{}

(1) 请注意,根据提案 N4164,已被接受到工作文件中,首选术语是 forwarding references .