4
#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): 错误:没有匹配的函数来调用 'f' f(n, std::move(n));

main.cpp(7,6):注意:候选模板被忽略:推断参数“T”的冲突类型(“int &”与“int”)

无效 f(T&& a, T&& b)

产生 1 个错误。

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

我的编译器是clang 4.0。

4

2 回答 2

4

编译器错误非常简单,T不能同时推断int&出来int。为了使它起作用,您必须提供一个额外的模板参数:

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

推理

处理基于引用折叠规则的转发引用(又名通用引用)时,模板参数推导遵循特殊规则

  • 你&&变成你&
  • 你&&&变成你&
  • 你&&&变成你&
  • U&& && 变成 U&&

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

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

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

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

此时,编译器举起双手尖叫“ dudeT的扣除类型必须与所有输入参数匹配”。

于 2017-02-23T08:51:51.770 回答
2

在使用转发引用(1)进行非肤浅的工作时,重要的是要了解它们的实际工作方式,而不是将它们视为“魔术”。

转发引用的所有“魔力”都是标准中的这条规则:

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

让我们看这个例子:

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

int i = 42;

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

当您调用foo(i):时会发生什么i是类型的左值int。由于转发引用规则,扣除将发生,好像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,首选术语是转发引用。

于 2017-02-23T08:54:05.017 回答