模板部分sorting – 为什么部分演绎在这里成功

考虑以下简单的问题(以模板问题为例):

#include <iostream> template <typename T> struct identity; template <> struct identity<int> { using type = int; }; template<typename T> void bar(T, T ) { std::cout << "a\n"; } template<typename T> void bar(T, typename identity<T>::type) { std::cout << "b\n"; } int main () { bar(0, 0); } 

clang和gcc都在那里打印“a”。 根据[temp.deduct.partial]和[temp.func.order]中的规则,为了确定偏序,我们需要综合一些独特的types。 所以我们有两次尝试:

 +---+-------------------------------+-------------------------------------------+ | | Parameters | Arguments | +---+-------------------------------+-------------------------------------------+ | a | T, typename identity<T>::type | UniqueA, UniqueA | | b | T, T | UniqueB, typename identity<UniqueB>::type | +---+-------------------------------+-------------------------------------------+ 

对于“b”的推论,根据Richard Corden的回答 ,expression式typename identity<UniqueB>::type被视为一个types,不被评估。 也就是说,这将被合成,就像它是:

 +---+-------------------------------+--------------------+ | | Parameters | Arguments | +---+-------------------------------+--------------------+ | a | T, typename identity<T>::type | UniqueA, UniqueA | | b | T, T | UniqueB, UniqueB_2 | +---+-------------------------------+--------------------+ 

很明显,扣除“b”失败。 这是两种不同的types,所以你不能将T推导出来。

但是,在我看来,对A的扣除应该是失败的。 对于第一个参数,你可以匹配T == UniqueA 。 第二个参数是一个非推导的上下文 – 如果UniqueA可以转换为identity<UniqueA>::type ,那么这种推导是否成功? 后者是替代失败,所以我不知道这个演绎是如何成功的。

在这种情况下,gcc和clang如何以及为什么更喜欢“a”超载?

正如在评论中所讨论的,我相信function模板部分sortingalgorithm有几个方面在标准中是不清楚的或者根本没有指定,这在你的例子中显示。

为了使事情更有趣,MSVC(我testing了12和14)拒绝呼叫模糊。 我不认为标准中有什么可以certificate哪种编译器是正确的,但是我想我可能知道差异来自哪里。 有关于下面的说明。

你的问题(和这一个 )挑战我做一些事情如何工作更多的调查。 我决定写这个答案不是因为我认为它是权威的,而是把我在一个地方find的信息(它不适合在评论中)组织起来。 我希望这会有用。


首先,提出的问题1391的决议。 我们在评论和聊天中进行了广泛的讨论。 我认为,虽然它提供了一些澄清,但也引入了一些问题。 它将[14.8.2.4p4]更改为(以粗体显示的新文字):

使用参数模板和参数模板中的相应types指定的每种types都用作PA的types。 如果特定的P包含参与模板参数推导的模板参数,那么P不用于确定sorting。

在我看来,这不是一个好主意,原因如下:

  • 如果P是非依赖的,它根本不包含任何模板参数,所以它也不包含参与任何参数推导的任何参数,这将使粗体语句适用于它。 然而,这将使template<class T> f(T, int)template<class T, class U> f(T, U)无序,这是没有意义的。 这可以说是对措辞的解释,但可能会造成混淆。
  • 它与用于确定sorting的概念混淆,这影响了[14.8.2.4p11]。 这使得template<class T> void f(T)template<class T> void f(typename A<T>::a)无序(从第一个到第二个成功推导template<class T> void f(typename A<T>::a) ,因为T不用于用于局部按照新的规则sorting,所以它可以保持没有价值)。 目前,我testing过的所有编译器报告第二个更专业。
  • 在下面的例子中,它将使#2#1更专业:

     #include <iostream> template<class T> struct A { using a = T; }; struct D { }; template<class T> struct B { B() = default; B(D) { } }; template<class T> struct C { C() = default; C(D) { } }; template<class T> void f(T, B<T>) { std::cout << "#1\n"; } // #1 template<class T> void f(T, C<typename A<T>::a>) { std::cout << "#2\n"; } // #2 int main() { f<int>(1, D()); } 

    #2的第二个参数不用于偏序排列,所以从#1#2推导成功,反之则不然)。 目前,这个呼吁是不明确的,应该保持如此。


看了Clang部分sortingalgorithm的实现后,我认为标准文本可以被改变以反映实际发生的情况。

离开[p4],并在[p8]和[p9]之间添加以下内容:

对于一个P / A对:

  • 如果P是非相关的,当且仅当PA是相同的types时,扣除被认为是成功的。
  • 将推导出的模板参数代入P中出现的未推导出的上下文中并不执行,也不影响扣除过程的结果。
  • 如果对于P所有模板参数成功地推导出模板参数值,除了仅出现在未推导的上下文中的那些模板参数,那么扣除被认为是成功的(即使在扣除过程结束时, P使用的一些参数仍然没有值那个特定的P / A对)。

笔记:

  • 关于第二个要点:[14.8.2.5p1]讨论了如何find模板参数值,这个将会使得P在replace推导出的值(称为推导出的A )后与A兼容 。 这可能会导致在部分sorting过程中实际发生的混淆; 没有替代进行。
  • 在某些情况下,MSVC似乎没有实现第三个重点。 有关详细信息,请参阅下一节。
  • 第二个和第三个要点也用于涵盖Pforms类似于A<T, typename U::b> ,这些情况未被问题1391中的措词所覆盖。

将当前[p10]更改为:

函数模板F至less与函数模板G一样专用当且仅当:

  • 对于用于确定sorting的每一对types,来自F的types至less与来自G的types一样专用,
  • 当使用变换后的F作为变元模板并且G作为参数模板进行演绎时,对于所有types的对都进行了推导之后,用于确定sorting的来自G的types中使用的所有模板参数都具有值,并且这些值在所有types的对中都是一致的。

如果F至less和G一样专业化, G至less不像F那么专业化,则FG 更专业化

使整个当前[p11]一个音符。

(1391至[14.8.2.5p4]的决议增加的注释也需要调整 – 对于[14.8.2.1]可以,但对[14.8.2.4]不可以。)


对于MSVC,在某些情况下,看起来P所有模板参数都需要在扣除该特定P / A对的过程中接收值,以便从A推导到P 我认为这可能是导致您的示例和其他示例出现分歧的原因,但至less有一个上述内容似乎不适用,所以我不确定相信什么。

另一个例子,上面的语句似乎适用:在您的示例交换结果中将template<typename T> void bar(T, T)更改为template<typename T, typename U> void bar(T, U)在Clang和GCC中不明确,但在MSVC中parsing为b

其中一个例子:

 #include <iostream> template<class T> struct A { using a = T; }; template<class, class> struct B { }; template<class T, class U> void f(B<U, T>) { std::cout << "#1\n"; } template<class T, class U> void f(B<U, typename A<T>::a>) { std::cout << "#2\n"; } int main() { f<int>(B<int, int>()); } 

如Cland和GCC中select#2 ,如预期,但MSVC拒绝呼叫模糊; 不知道为什么。


标准中描述的部分sortingalgorithm提到了合成唯一的types,值或类模板以生成参数。 铿pipe理…通过…不合成任何东西。 它只是使用依赖types的原始forms(如声明的),并以两种方式匹配它们。 这是有道理的,因为replace合成types不会添加任何新的信息。 它不能改变A型的forms,因为通常没有办法告诉被replace的forms能够解决什么样的具体types。 合成的types是未知的,这使得它们与模板参数非常相似。

当遇到一个非推断上下文的P ,Clang的模板参数推导algorithm会简单地跳过它,通过返回该特定步骤的“成功”。 这不仅发生在部分sorting过程中,而且发生在所有types的推导中,而不仅仅是函数参数列表中的顶层,而是以复合types的forms遇到非推导的上下文时recursion地发生。 出于某种原因,我第一次看到它,我感到很惊讶。 考虑到这一点,它当然是有道理的,而且是按照标准([14.8.2.5p4] 中没有参与types演绎 )。

这与Richard Corden对他的回答 的评论是一致的 ,但是我必须真正看到编译器代码来理解所有的含义(不是他的回答的错误,而是我自己的程序员在代码中的思考和所有这些)。

我在这个答案中包含了更多关于Clang的实现的信息。

我相信关键是有以下陈述:

第二个参数是一个非推导的上下文 – 如果UniqueA可以转换为identity :: type,那么这种推导是否成功?

types扣除不会执行“转换”的检查。 这些检查使用真实的显式和推论的参数作为重载决议的一部分。

这是我select要调用的函数模板所采取步骤的摘要(所有参考文献摘自N3937,〜C ++ '14):

  1. 显式参数被replace,并检查结果函数types是否有效。 (14.8.2 / 2)
  2. types扣除被执行并且所得到的推导出的参数被replace。 同样的结果types必须是有效的。 (14.8.2 / 5)
  3. 步骤1和步骤2中成功的function模板是专用的,并且包含在用于重载parsing的重载集中。 (14.8.3 / 1)
  4. 转换序列通过重载分辨率进行比较。 (13.3.3)
  5. 如果两个函数特化的转换序列不是“更好的”,则使用偏序sortingalgorithm来find更专用的函数模板。 (13.3.3)
  6. 部分sortingalgorithm只检查types扣减是否成功。 (14.5.6.2/2)

编译器已经知道第4步,当使用真正的参数时,可以调用这两个特化。 正在使用步骤5和6来确定哪个function更专业化。