c++ - 模板部分排序-为什么部分推理在这里成功

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

考虑以下简单(就模板问题而言)示例:

#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]中的规则,要确定偏序,我们需要综合一些唯一类型。因此,我们有两种尝试进行推导:
+---+-------------------------------+-------------------------------------------+
|   | Parameters                    | Arguments                                 |
+---+-------------------------------+-------------------------------------------+
| a | T, typename identity<T>::type | UniqueA, UniqueA                          |
| b | T, T                          | UniqueB, typename identity<UniqueB>::type |
+---+-------------------------------+-------------------------------------------+

为了对“b”进行推论,根据Richard Corden's answer,将表达式typename identity<UniqueB>::type视为类型,并且不对其进行求值。也就是说,这将被合成为:
+---+-------------------------------+--------------------+
|   | Parameters                    | Arguments          |
+---+-------------------------------+--------------------+
| a | T, typename identity<T>::type | UniqueA, UniqueA   |
| b | T, T                          | UniqueB, UniqueB_2 |
+---+-------------------------------+--------------------+

显然,对“b”的推论失败。这是两种不同的类型,因此您无法将T推论给它们两者。

但是,在我看来,对A的推论应该失败。对于第一个参数,您将匹配T == UniqueA。第二个参数是非推论上下文-如果UniqueA可转换为identity<UniqueA>::type,推论成功吗?后者是替代失败,因此我也看不到这种推论如何成功。

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

最佳答案

正如评论中所讨论的那样,我相信功能模板部分排序算法的某些方面在标准中尚不清楚或根本没有指定,这在您的示例中得以展示。

为了使事情变得更加有趣,MSVC(我测试了12和14)拒绝了该电话的通话。我认为标准中没有什么可以结论性地证明哪个编译器是正确的,但是我想我可能会知道差异的根源。下面有一个注释。

您的问题(和this one)挑战了我对事情的工作方式进行更多调查。我决定写这个答案并不是因为我认为它是权威的,而是将我发现的信息组织在一个地方(它不适合放在注释中)。我希望它会有用。

首先,为issue 1391提出的解决方案。我们在评论和聊天中进行了广泛的讨论。我认为,尽管确实提供了一些说明,但同时也引入了一些问题。它将[14.8.2.4p4]更改为(新文本以粗体显示):

Each type nominated above from the parameter template and the corresponding type from the argument template are used as the types of P and A. If a particular P contains no template-parameters that participate in template argument deduction, that P is not used to determine the ordering.



我认为这不是一个好主意,原因如下:
  • 如果P是非依赖性的,则它根本不包含任何模板参数,因此也不包含任何参与参数推导的参数,这将使粗体语句适用于该参数。但是,这会使template<class T> f(T, int)template<class T, class U> f(T, U)无序,这没有任何意义。这可以说是措辞的解释问题,但可能引起困惑。
  • 它与用来确定顺序的概念弄混了,这影响了[14.8.2.4p11]。这使template<class T> void f(T)template<class T> void f(typename A<T>::a)处于无序状态(推论从第一个到第二个成功,因为T并未根据新规则用于部分排序的类型中使用,因此可以保持不带任何值)。目前,我测试过的所有编译器都报告第二种更为专业。
  • 在以下示例中,它将使#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,反之则不行)。当前,该 call 是模棱两可的,可以说应该保持不变。


  • 在查看了Clang的部分排序算法的实现之后,这就是我认为可以更改标准文本以反射(reflect)实际情况的方式。

    保持[p4]不变,并在[p8]和[p9]之间添加以下内容:

    For a P / A pair:

    • If P is non-dependent, deduction is considered successful if and only if P and A are the same type.
    • Substitution of deduced template parameters into the non-deduced contexts appearing in P is not performed and does not affect the outcome of the deduction process.
    • If template argument values are successfully deduced for all template parameters of P except the ones that appear only in non-deduced contexts, then deduction is considered successful (even if some parameters used in P remain without a value at the end of the deduction process for that particular P / A pair).


    笔记:
  • 关于第二个要点:[14.8.2.5p1]讨论如何找到模板参数值,该参数值将替换推导值(称为推导P)后与A兼容,从而使A成为可能。这可能导致对部分订购期间实际发生的事情感到困惑;没有替代。
  • MSVC在某些情况下似乎未实现第三个要点。有关详细信息,请参见下一部分。
  • 第二和第三个项目符号点还旨在涵盖P具有类似A<T, typename U::b>的形式的情况,这些问题未包含在第1391期的措辞中。

  • 将当前的[p10]更改为:

    Function template F is at least as specialized as function template G if and only if:

    • for each pair of types used to determine the ordering, the type from F is at least as specialized as the type from G, and,
    • when performing deduction using the transformed F as the argument template and G as the parameter template, after deduction is done for all pairs of types, all template parameters used in the types from G that are used to determine the ordering have values, and those values are consistent across all pairs of types.

    F is more specialized than G if F is at least as specialized as G and G is not at least as specialized as F.



    将整个当前[p11]记录下来。

    (由1391号决议添加到[14.8.2.5p4]的注释也需要进行调整-适用于[14.8.2.1],但不适用于[14.8.2.4]。)

    对于MSVC,在某些情况下,看起来P中的所有模板参数都需要在推导过程中接收该特定P/A对的值,以便推论成功地从A转换为P。我认为这可能是导致您的示例与其他示例之间实现差异的原因,但是我至少看到一种情况,上述情况似乎并不适用,因此我不确定该相信什么。

    上面的语句似乎确实适用的另一个示例:在示例中将template<typename T> void bar(T, T)更改为template<typename T, typename U> void bar(T, U)会交换结果:在Clang和GCC中,调用是模棱两可的,但在MSVC中,解析为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>());
    }
    

    如预期的那样,这将在Clang和GCC中选择#2,但是MSVC拒绝该调用是模棱两可的。不知道为什么。

    标准中描述的部分排序算法涉及合成唯一的类型,值或类模板以生成自变量。 Clang通过...不合成任何东西来进行管理。它仅使用依赖类型的原始形式(如声明的那样),并以两种方式进行匹配。这是有道理的,因为替换综合类型不会添加任何新信息。它无法更改A类型的形式,因为通常无法确定替换形式可以解析为哪些具体类型。合成的类型是未知的,这使其与模板参数非常相似。

    当遇到作为非推导上下文的P时,Clang的模板参数推导算法只是通过为该特定步骤返回“成功”来跳过它。这不仅在部分排序期间发生,而且在所有类型的推导中不仅发生在函数参数列表的顶级,而且在遇到复合类型形式的非推导上下文时都以递归方式发生。由于某种原因,我第一次见到它时感到很惊讶。考虑一下,它当然是有道理的,并且符合标准([14.8.2.5p4]中不参与类型推导)。

    这与Richard Corden'shis answer的注释是一致的,但是我实际上必须看到编译器代码才能理解所有含义(这不是他的回答的错,而是我自己的-程序员在代码等方面的思考)。

    我在this answer中包含了有关Clang实现的更多信息。

    关于c++ - 模板部分排序-为什么部分推理在这里成功,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31394260/

    相关文章:

    c++ - 比较文本和整数的多个 if 语句

    html - Go template/html 迭代从结构生成表

    c - 转义常规字符

    c++ - 堆上的对象和引用

    c++ - 初始化派生类虚函数返回的内联静态变量的最佳方法

    c++ - 在 Mac 上使用 CDT 的 Eclipse

    c++ - 使用基于数组和范围的 For 循环替换一些基本代码行

    C++单例模板类继承

    c++ - 无法将 std::function move 到 map 中

    c++ - MSVC 用 double 支撑初始化似乎违反了标准?