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

Mar*_*son 11 c++ templates partial-ordering language-lawyer overload-resolution

在阅读了如何使这些std :: function参数明确无误后,我完全糊涂了到目前为止,我认为我理解函数模板的部分排序是什么,但在阅读了这个问题后,我写了三个例子来检查编译器的行为,并且收到的结果很难让我理解.

示例#1

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

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

int main()
{
  int i;
  foo<int>(i); // error: call is ambiguous! 
}
Run Code Online (Sandbox Code Playgroud)

问题:这两个功能都是可行的,这是显而易见的,但不是那个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]
}
Run Code Online (Sandbox Code Playgroud)

问题:如果示例#1中的T&T不明确,那为什么这里没有一个调用是不明确的?X<int>从constructible 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]
}
Run Code Online (Sandbox Code Playgroud)

问:现在,这是类似的场景,以"匹配拉姆达[](int){}反对std::function<void(int&)>std::function<void(int)>"自挂的问题.为什么在两个调用中都会选择更专业的函数模板?是因为转换顺序是相同的,所以部分排序开始重要吗?

所有测试都在GCC 4.9.0上完成-std=c++11,没有额外的标志.

Col*_*mbo 7

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

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

给定这些定义,如果对于所有参数 i,可行函数F1被定义为比另一个可行函数更好的函数,不是比转换序列更差的转换序列,然后- 对于某些参数j,是比转换序列更好的转换序列,或者,如果不是这样,- 上下文是通过用户定义的转换初始化(参见8.5,13.3.1.5和13.3.1.6)以及从返回类型到目标类型的标准转换序列(即,实体的类型是initialized)是一个比返回类型到目标类型的标准转换序列更好的转换序列.[例如:F2ICSi(F1)ICSi(F2)
ICSj(F1)ICSj(F2)
F1F2

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
Run Code Online (Sandbox Code Playgroud)

- 结束示例 ]或者,如果不是,
- F1是非模板函数,F2是函数模板特化,或者,如果不是,
- F1和F2是函数模板特化,F1的函数模板更专业根据14.5.6.2中描述的部分排序规则,比F2的模板.


情况1:

但是不是一个T&比专业更专业的人T

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

在完成部分排序之前,对用于部分排序的类型执行某些转换:
- 如果P是引用类型,P则替换为引用的类型.
- 如果A是引用类型,A则替换为引用的类型.

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

案例2:

这里不需要部分订购.用户定义的转换X<int>X<int&>比转换X<int>为更差的等级X<int>- 后者按[over.ics.user]/4给出完全匹配等级:

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

因此,这显然不是一个更好的转换X<int>X<int&>,其中有转换等级.同去反之亦然,对于X<int&>X<int>.

案例3:

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

template <class T> void bar(X<T>);  // #1

template <class T> void bar(X<T&>); // #2

// Call:
bar<int>( F<int>() );
Run Code Online (Sandbox Code Playgroud)

返回[temp.deduct.partial],执行类型扣除.调用它的唯一类型Unique是为每个参数模板的模板参数合成的.执行以下具有相应结果的过程 - 请注意,使用F<int>with F<int&>(以及任何其他专业化F)调用时,步骤完全相同:

  1. 模板#1作为参数模板,模板#2作为参数模板,X<Unique&>推导出X<T>,屈服T=Unique&.另一方面,
  2. 将模板#2作为参数模板(作为参数模板的模板#1 X<Unique>)推导出来X<T&>,这导致扣减失败.

我们可以看到模板#2更专业.当在步骤1中用作参数模板时,推导成功,而对于模板#1作为步骤2中的参数模板,推导失败.因此,调用第二个更专业的函数模板的特化.