【问题标题】:Why do arrays of const preferentially bind to const T& parameters instead of T&& parameters?为什么 const 的数组优先绑定到 const T& 参数而不是 T&& 参数?
【发布时间】:2012-09-04 16:20:12
【问题描述】:

重载带有“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&amp; 重载将被调用为 const 左值的参数,T&amp;&amp; 重载将被调用用于所有其他参数类型。但是考虑一下当我们使用 const 和非 const 内容的数组调用 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&amp; 重载。简单的答案是 constArray 有一个 const ,但我认为这太简单了。推导出的类型 T(无论选择的模板如何)是“5 个 const int 的数组”,因此 const T&amp; 重载中的 param 的类型将是“对 5 个 const ints 的 const 数组的引用”。但是名为 constArray 的数组本身并未声明为 const。那么为什么对func(constArray) 的调用不调用T&amp;&amp; 重载,从而为param 生成“引用5 个常量整数数组”的类型?

这个问题的动机是与c++ template function argument deduce and function resolution 上的问题相关的讨论,但我认为该线程在其他问题上偏离了方向,并没有澄清我现在在这里提出的问题。

【问题讨论】:

  • 那是因为引用崩溃了!

标签: c++ c++11


【解决方案1】:

在函数参数列表中(以及其他任何地方),数组类型上的 cv 限定符被改组以限定数组元素类型。例如,使用T = int [5]const T &amp; 将转换为int const (&amp;) [5]

3.9.3 CV 限定符 [basic.type.qualiifier]

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 &amp; 模板,T2 为T &amp;&amp; 模板;也就是说,它们的参数类型是 T1 := const T &amp; 和 T2 := T &amp;&amp;。然后转换后的参数类型 (14.5.6.2:3) 可以写成 A1 := const C &amp;, A2 := D &amp;&amp; 合成类型 C, D

现在,我们尝试将 T1 与 T2 (14.8.2.4:2) 排序,首先使用 A1 作为参数模板,P2 作为参数模板。我们删除引用 (14.8.2.4:5) 给 A1 -> const C 和 T2 -> T,然后删除 cv-qualification (14.8.2.4:7) 给 A1 -> C 和 T2 -> @987654338 @。模板T 可以推导出为C (14.8.2.4:8) 所以A1 至少和P2 一样专业;相反,A2 -> D -> D, P1 -> const T -> T, 和 T 可以推导出为 D,所以 A2 至少和 P1 一样特化.

这通常意味着没有一个比另一个更专业;但是,因为 PA 类型是引用类型,所以适用 14.8.2.4:9,并且由于 A1 是左值引用而 P2 不是,因此 T1 被认为比 T2 更专业。 (引用类型之间的联系也可以通过同一子句下的 cv 限定来打破。)

【讨论】:

  • 感谢您引用标准中解决此问题的适当位置。我正在查看引用初始化规则,这离正确的地方很远。 (我现在看到 8.3.4/1 中有相同的信息。)
  • 两个重载具有相同的签名。为什么一个更可取?答案一定与模板专业化的部分排序有关,但我正在努力将这个论点拼凑在一起。
  • @KerrekSB:我同意,很高兴看到这一点,因为这两个模板都接受 const 和非 const 左值和右值。直观地说,T&& 版本似乎不如 const T& 版本那么专业,这与我引用的编译器的行为是一致的,但不那么随意的解释会非常有帮助。
  • @KnowItAllWannabe:我必须备份一下 - 我认为实际上不涉及部分排序(因为两个模板具有相同数量的模板参数);相反,这是一个(更简单的)问题,即哪个专业更专业。
  • @KerrekSB 关键规则是 14.8.2.4:9(右值引用不如左值引用专门)。我已经完成了上面的部分排序算法。
【解决方案2】:

您混淆了右值引用(如int&amp;&amp;)和通用引用(由模板参数构成,如template &lt;typename T&gt; ... T&amp;&amp;)。

右值引用确实不绑定到左值。但是通用引用绑定到任何东西。问题只是谁更适合。

你的类型是int const [5]。现在让我们看看:

  • 反对T const &amp;:匹配T = int[5]

  • 反对T &amp;&amp;:匹配T = int const (&amp;)[5]

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

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

(显然array不匹配const T &amp;,因为它不是const,只能匹配T&amp;&amp;,推导出T = int (&amp;)[5])。

【讨论】:

  • 显然前者更合适!。你认为它清楚吗?我不这么认为。
  • @Nawaz: "很明显" = "有人应该查一下扣分排名规则"。但是,是的,两者的区别在于 CV 转换,它在指标中添加了“1”。
  • @Nawaz:真正的问题是在T &amp;&amp;T const &amp;&amp; 之间谁会赢:我认为绑定到引用算作立即匹配,所以我敢打赌那将是模棱两可的。
  • 为什么推导出 T 为 int[5]? const 不是顶级的,因此不应该被剥离,IMO。我相信对于 const T& 模板,应该将 T 推导出为 const int[5] 。我同意你的观点,对于 T&& 模板,推导的类型是 const int (&)[5]。你能解释一下为什么你认为适用于数组内容的常量(不是适用于数组本身)会在类型推断期间被剥离吗?
  • @KnowItAllWannabe:啊,我误解了你的问题。好的,您已经从另一个答案中得到了答案。但这里重要的不是论证推导(我们已经解决),而是哪个专业化更专业化的问题。
猜你喜欢
  • 1970-01-01
  • 2019-06-26
  • 2012-08-02
  • 2017-01-11
  • 1970-01-01
  • 2015-02-27
  • 2019-09-14
  • 2017-10-22
  • 2012-03-29
相关资源
最近更新 更多