11

阅读问题如何使这些 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&andT是不明确的,那么为什么这里没有一个 call 是不明确的?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没有额外的标志。

4

2 回答 2

7

重载解析试图找到这样的最佳函数:

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

鉴于这些定义,一个可行函数F1被定义为比另一个可行函数更好的函数,F2如果对于所有参数 iICSi(F1)不是比 更差的转换序列ICSi(F2),然后
- 对于某些参数jICSj(F1)是比 更好的转换序列ICSj(F2),或者,如果并非如此,
——上下文是用户定义的转换(见 8.5、13.3.1.5 和 13.3.1.6)和从返回类型F1到目标类型(即实体的类型)的标准转换序列initialized) 是一个比从返回类型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

end example ] 或者,如果不是这样,
— F1 是非模板函数,F2 是函数模板特化,或者,如果不是这样,
— F1 和 F2 是函数模板特化,并且 F1 的函数模板更加特化根据 14.5.6.2 中描述的部分排序规则,而不是 F2 的模板。


情况1:

但不是T&比那个更专业T吗?

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

在完成偏序之前,对用于偏序的类型执行某些转换:
— 如果P是引用类型,P则替换为所引用的类型。
— 如果A是引用类型,A则替换为引用的类型。

由于参数模板的参数完全相同,因此不难看出相互推导在两种情况下都是成功的——因此两个模板都不比另一个模板更专业。

案例二:

这里不需要部分排序。用户定义的转换 from X<int>toX<int&>的排名比转换X<int>为差X<int>- 后者由 [over.ics.user]/4 给出 Exact Match 排名:

将类类型的表达式转换为相同的类类型被赋予精确匹配等级,[…]

因此,它显然是比X<int>to更好的转换X<int&>,它具有转换等级。反之亦然,对于X<int&>to 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>执行以下具有相应结果的过程 - 请注意,在调用 with和 with F<int&>(以及 的任何其他特化)时,步骤完全相同F

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

正如我们所见,模板 #2 更加专业。当在步骤 1 中用作参数模板时,推导成功,而对于模板 #1 作为步骤 2 中的参数模板,推导失败。因此,调用了第二种更专业的功能模板的专业化。

于 2014-10-11T19:16:46.707 回答
1

示例 1:

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

示例 2:

在这里你告诉编译器第二个重载qux需要 aX<T &>所以他知道你想用 a 构造这个对象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:

同样的问题。

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

于 2014-10-11T19:05:40.570 回答