两个函数模板何时被视为部分有序且何时不明确?

时间:2014-10-11 18:23:37

标签: c++ templates language-lawyer overload-resolution partial-ordering

我在阅读问题How to make these std::function parameters unambiguous?之后感到非常困惑,到目前为止,我认为我理解函数模板的部分排序是什么,但在阅读了这个问题之后我写了三个检查编译器行为的示例,我收到的结果很难理解。

示例#1

template <class T>
void foo(T) {}

template <class T>
void foo(T&) {}

int main()
{
  int i;
  foo<int>(i); // error: call is ambiguous! 
}

问题:这两个函数都是可行的,这是显而易见的,但不是T&T更专业的函数吗?相反,编译器会引发模糊调用错误。

示例#2

#include <iostream>

template <class T>
struct X {};

template <>
struct X<int> 
{
  X() {}
  X(X<int&> const&) {} // X<int> is constructible from X<int&>
  // note: this is not a copy constructor!
};

template <>
struct X<int&> 
{
  X() {}
  X(X<int> const&) {} // X<int&> is constructible from X<int>
  // note: this is not a copy constructor!
};

template <class T>
void bar(X<T>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

template <class T>
void bar(X<T&>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

int main()
{
  bar<int>(X<int>());   // calls void bar(X<T>) [with T = int]

  bar<int>(X<int&>());  // calls void bar(X<T&>) [with T = int]
}

问题:如果示例#1中的T&T不明确,那么为什么这里没有一个调用是不明确的? X<int>可构建X<int&>,并且X<int&>可以X<int>构建,这要归功于提供的构造函数。是因为编译器生成的X<int>::X(X<int> const&)拷贝构造函数是一个更好的转换序列而不是X<int>::X(X<int&> const&),(如果是这样,是什么让它变得更好,请注意参数是按值传递的),所以专业化的顺序根本不重要?

示例#3

#include <iostream>

// note: a new type used in constructors!
template <class U>
struct F {};

template <class T>
struct X
{
  X() {}

  template <class U>
  X(F<U> const&) {}  // X<T> is constructible from any F<U>
  // note: it takes F type, not X!
};

template <class T>
void qux(X<T>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

template <class T>
void qux(X<T&>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

int main()
{
  qux<int>(F<int>());  // calls void qux(X<T&>) [with T = int]

  qux<int>(F<int&>()); // calls void qux(X<T&>) [with T = int]
}

问题:现在这与“匹配来自问题链接的[](int){}std::function<void(int&)>的lambda std::function<void(int)>相似。为什么在两次调用中都会选择更专业的函数模板?是因为转换序列是相同的,所以部分排序开始重要吗?

所有测试都在GCC 4.9.0上进行,-std=c++11并没有额外的标记。

2 个答案:

答案 0 :(得分:7)

重载决策尝试找到这样的最佳函数:

(1)[over.match.best] / 1:

  

鉴于这些定义,可行函数F1被定义为a   如果适用于所有参数,则比另一个可行函数F2具有更好的功能    i ICSi(F1)转换序列不是ICSi(F2),然后是 - 对于某些参数 j ,{{1}是一个更好的转换   序列比ICSj(F1),或者,如果不是,则 - 上下文是一个   通过用户定义的转换进行初始化(见8.5,13.3.1.5和   13.3.1.6)和从ICSj(F2)的返回类型到目标类型的标准转换序列(即,实体的类型是   初始化)是比标准更好的转换序列   转换类型从F1的返回类型到目标   类型。
[例如:

F2
     

- 结束示例]或者,如果不是这样,则 - F1是非模板   函数和F2是函数模板特化,或者,如果不是   那,, - F1和F2是功能模板专业化,而且   F1的功能模板比F2的模板更专业   根据14.5.6.2中描述的偏序规则。


案例1:

  

但不是struct A { A(); operator int(); operator double(); } a; int i = a; // a.operator int() followed by no conversion is better // than `a.operator double()` // followed by a conversion to `int` float x = a; // ambiguous: both possibilities require conversions, // and neither is better than the other T&更专业的人吗?

根据重载决策,没有转换更好(两者都是身份转换,这是完全匹配),并且由于(1)中没有其他子弹适用,因此完成了部分排序。 [temp.deduct.partial] / 5表示为了部分排序,引用被它们引用的类型替换:

  

在部分排序完成之前,某些转换是   对用于部分排序的类型执行:
- 如果T是a   引用类型P将被引用的类型替换。
- 如果P是a   引用类型A将被引用的类型替换。

由于参数模板的参数完全相同,因此不难发现两种方式相互之间的推论是成功的 - 因此两种模板都不比另一种更专业。

案例2:

此处不需要部分订购。从AX<int>的用户定义转化比将X<int&>转换为X<int>的排名更差 - 后者通过[over.ics]给出完全匹配排名。用户/ 4:

  

将类类型的表达式转换为相同的类类型   给出完全匹配等级,[...]

因此,它显然比X<int>转换为具有转化排名的X<int>更好。同样,反之亦然,X<int&>X<int&>

案例3:

第三种情况与第一种情况类似。 X<int>X<int>都有一个构造函数模板,可以对X<int&>进行任意特化。 (1)告诉我们,由于没有任何转换序列以任何方式优于其他转换序列(事实上,它们完全相同),因此选择了更专业的模板。

F

返回[temp.deduct.partial],执行类型扣除。一个唯一的类型,称为template <class T> void bar(X<T>); // #1 template <class T> void bar(X<T&>); // #2 // Call: bar<int>( F<int>() ); ,是为每个参数模板的模板参数合成的。执行以下具有相应结果的步骤 - 请注意,使用UniqueF<int>(以及F<int&>的任何其他专业化)调用时,步骤完全相同:

  1. 模板#1作为参数模板,模板#2作为参数模板,F是针对X<Unique&>推导出来的,产生X<T>。另一方面,
  2. 模板#2作为模板#1的参数模板作为参数模板,T=Unique&推导出X<Unique>导致扣减失败
  3. 我们可以看到模板#2更专业。当在步骤1中用作参数模板时,推导成功,而对于模板#1作为步骤2中的参数模板,推导失败。因此,第二个更专业的函数模板的特化称为。

答案 1 :(得分:1)

示例1:

编译器无法通过引用或值通知{1}}。如果您使用a专门化模板,他会很容易理解,因为函数调用语法会有所不同T *

示例2:

在这里,您告诉编译器foo(&a)的第二个重载需要qux,因此他知道您想要使用X<T &>构造此对象。没有含糊之处。但如果你这样做:

T &

你最终会遇到同样的问题

示例3:

同样的问题。

我不知道它是否非常清楚如果有人可以改进我的答案,这对作者可能有用