为什么 const 数组优先绑定到 const T& 参数而不是 T&& 参数?

Why do arrays of const preferentially bind to const T& parameters instead of T&& parameters?

本文关键字:参数 const 数组 绑定 为什么      更新时间:2023-10-16
使用

"T&&"参数重载函数模板通常是一个坏主意,因为它可以绑定到任何内容,但假设我们还是这样做:

template<typename T>
void func(const T& param)
{
  std::cout << "const T&n";
}
template<typename T>
void func(T&& param)
{
  std::cout << "T&&n";
}

我的理解是,对于常量左值的参数,将调用const T&重载,而T&&重载将针对所有其他参数类型调用。 但是考虑一下当我们用常量和非常量内容的数组调用func时会发生什么:

int main()
{
  int array[5] = {};
  const int constArray[5] = {};
  func(array);             // calls T&& overload
  func(constArray);        // calls const T& overload
}

VC10、VC11 和 gcc 4.7 与所示结果一致。 我的问题是为什么第二个调用会调用const T&重载。 简单的答案是constArray有一个常量,但我认为这太简单了。 推导的类型 T(无论选择哪个模板(是"5 个 const int 的数组",因此const T&重载中的 param 类型将是"引用 5 个 const int 的 const 数组"。 但是名为 constArray 的数组本身并没有声明为 const。 那么,为什么对 func(constArray) 的调用不调用T&&重载,从而产生一个用于param"引用 5 个 const 整数数组"的类型呢?

这个问题的动机是与 c++ 模板函数参数推导和函数解析中的问题相关的讨论,但我认为该线程在其他问题上偏离了方向,并没有澄清我现在在这里提出的问题。

在函数参数列表(以及其他任何地方(中,数组类型的 cv 限定被打乱以限定数组元素类型。 例如,对于 T = int [5]const T & 将转换为 int const (&) [5]

3.9.3 CV限定符[基本类型限定符]

2 - [...]应用于数组类型的任何 cv 限定符都会影响数组元素类型,而不是数组类型 (8.3.4(。

因此,使用类型 int const [5] 的参数对 func 的调用被推导为对以下任一参数的调用:

void func<int [5]>(int const (&) [5])
void func<int const (&) [5]>(int const (& &&) [5])
// where the above collapses to
// 'void func<int const (&) [5]>(int const (&) [5])'

这两种重载都是可行的,但前者是首选:

设 T1 为const T &模板,T2 为T &&模板;也就是说,它们的参数类型为 T1 := const T & 和 T2 := T && 。 然后转换后的参数类型(14.5.6.2:3(可以写成A1 := const C &,A2 := D &&用于合成类型CD

现在,我们尝试对 T1 与 T2 (14.8.2.4:2( 进行排序,首先使用 A1 作为参数模板,使用 P2 作为参数模板。 我们删除给出 A1 -> const C 和 T2 -> T 的引用 (14.8.2.4:5(,然后删除给出 A1 -> C 和 T2 -> T 的 cv 限定 (14.8.2.4:7(。 模板T可以推导出为C(14.8.2.4:8(,因此A1至少与P2一样专业;相反,A2 -> D -> D、P1 -> const T -> TT 可以推导出为D,所以 A2 至少和 P1 一样专业

这通常意味着两者都不比另一个更专业;但是,由于PA类型是引用类型14.8.2.4:9适用,并且由于A1是左值引用而P2不是,因此T1被认为比T2更专业。 (参考类型之间的联系也可以通过同一条款下的简历限定来打破。

您混淆了右值引用(如int&&(和通用引用(由模板参数制成,如template <typename T> ... T&&(。

右值引用确实不绑定到左值。但是通用引用与任何东西绑定。问题只是谁是更好的匹配。

您拥有的类型是 int const [5] .现在让我们看看:

  • 对阵T const &:与T = int[5]比赛。

  • 对阵T &&:与T = int const (&)[5]比赛。

前者在以下意义上是更好的匹配:两个模板产生相同的重载。但T = int[5]T = int const (&)[5]更专业。你可以看到这一点,因为T = int const (&)[5]可以通过U = int[5]实现T = U const &

请注意,若要将左值绑定到通用引用,必须将类型本身推导为引用类型。

(显然arrayconst T &不匹配,因为它不是常量。它只能匹配T&&,推导出T = int (&)[5](。