何时将两个函数模板视为部分有序,何时不明确



读完问题 如何使这些 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! 
}

问题:这两个功能都是可行的,这是显而易见的,但是采用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]
}

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

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

重载分辨率尝试找到最佳函数,如下所示:

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

根据这些定义,一个可行的函数F1被定义为 比另一个可行的函数更好的函数 F2 对于所有参数 i,ICSi(F1) 不是一个比 ICSi(F2) 更糟糕的转换序列,然后
— 对于某些参数 j,ICSj(F1) 是一个更好的转换 序列比ICSj(F2),或者,如果不是那样,
— 上下文是一个 通过用户定义的转换进行初始化(请参阅 8.5、13.3.1.5 和 13.3.1.6(和从返回类型F1到目标类型的标准转换序列(即,实体的类型 初始化(是比标准更好的转换序列 从返回类型F2到目标的转换序列 类型。
[ 示例:

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

结束示例 ] 或者,如果不是这样,
— F1 是非模板 函数和 F2 是函数模板专用化,或者,如果不是 —
F1 和 F2 是函数模板专用化,并且 F1 的函数模板比 F2 的模板更专业 根据14.5.6.2中描述的部分排序规则。


案例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>() );

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

    模板 #1 作为参数模板,模板
  1. #2 作为参数模板,X<Unique&>根据X<T>推导,产生T=Unique&。另一方面
  2. 模板 #2 作为参数模板,模板
  3. #1 作为参数模板,X<Unique>根据X<T&>推导,导致演绎失败

正如我们所看到的,模板#2更加专业。在步骤 1 中用作参数模板时,扣除成功,而对于步骤 2 中的模板 #1 作为参数模板,扣除失败。因此,称为第二个更专业的函数模板专用化。

示例 1 :

编译器无法知道是要通过引用还是按值传递a。如果你用T *专门化你的模板,他会很容易知道,因为函数调用语法会foo(&a)不同。

示例 2 :

在这里,您告诉编译器qux的第二次重载需要X<T &>,因此他知道您要使用T &构造此对象。没有歧义。但是如果你这样做:

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; }

你最终会遇到同样的问题

例3:

同样的问题。

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

相关内容

  • 没有找到相关文章

最新更新