为什么左值会明确选择左值引用重载而不是转发引用重载?

Why is an lvalue-ref overload unambiguously chosen over a forwarding-ref overload for an lvalue?

看看这两个重载函数模板:

template <class T>
int foo(T& x) {  // #1
  return 1;
}
template <class T>
int foo(T&& x) {  // #2
  return 2;
}

我按以下方式调用foo

int i;
foo(i);  // calls #1

并且明确选择了重载#1:https://gcc.godbolt.org/z/zchK1zxMW

这似乎是一种正确的行为,但我不明白为什么会发生这种情况(实际上我的代码并不是我所期望的)。

来自 Overload resolution:

If any candidate is a function template, its specializations are generated using template argument deduction, and such specializations are treated just like non-template functions except where specified otherwise in the tie-breaker rules.

好的,让我们生成专业化。 #1 很简单,它变成 int foo(int& x)。 #2 适用特殊扣除规则,因为它是转发参考。 i 是一个左值,因此 T 被推导为 int& 并且 T&& 变为 int& &&,在引用折叠之后变为 int&,产生结果 int foo(int& x)。这与 #1 完全相同!

所以我希望看到一个模棱两可的调用,但这并没有发生。谁能解释一下为什么?此处使用什么决胜局来挑选最佳可行功能?

另见related discussion in Slack,有一些想法。

非语言律师的回答是,这个案件有一个决胜规则。

充分理解标准措辞以解码它需要一个简短的书章。但是,当推断出 T&&T& 重载是在左值和其他所有关系之间选择的选项时,T& 获胜。

这样做是为了 (a) 使通用引用起作用,同时 (b) 如果您想单独处理左值引用,则允许您重载它们。

决胜局来自模板函数重载“更专业”的排序规则。对于指针,T* 优于 T 的原因相同,即使 T=Foo*T=Foo 都提供相同的函数参数。对模板参数进行二次排序,T 可以模拟 T* 的事实意味着 T* 更专业(或者更确切地说不是,标准中的措辞很尴尬)。一条额外的规则说明左值 T& 胜过 T&& 在同一部分。