【问题标题】:Partial ordering with function template having undeduced context具有未推断上下文的函数模板的部分排序
【发布时间】:2010-11-13 21:34:25
【问题描述】:

在阅读另一个问题时,我遇到了偏序问题,我将其缩减为以下测试用例

template<typename T>
struct Const { typedef void type; };

template<typename T>
void f(T, typename Const<T>::type*) { cout << "Const"; } // T1

template<typename T>
void f(T, void*) { cout << "void*"; } // T2

int main() {
  // GCC chokes on f(0, 0) (not being able to match against T1)
  void *p = 0;
  f(0, p);
}

对于这两个函数模板,进入重载决议的特化的函数类型是void(int, void*)。但是偏序(根据 Comeau 和 GCC)现在说第二个模板更专业。但为什么呢?

让我通过部分排序并显示我有问题的地方。可能Q 是一个独特的虚构类型,用于根据14.5.5.2 确定偏序。

  • T1(插入 Q)的转换参数列表:(Q, typename Const&lt;Q&gt;::type*)。参数的类型是AT = (Q, void*)
  • T2 的转换参数列表(Q 插入):BT = (Q, void*),这也是参数的类型。
  • T1 的未转换参数列表:(T, typename Const&lt;T&gt;::type*)
  • T2 的未转换参数列表:(T, void*)

由于 C++03 没有明确说明这一点,我确实使用了我在几个缺陷报告中读到的意图。 T1 的上述转换参数列表(我称为AT)用作14.8.2.1 的参数列表“从函数调用中推导出模板参数”

14.8.2.1 不再需要转换 ATBT 本身(例如,删除引用声明符等),并直接转到 14.8.2.4,每个 A / P 对独立做类型推导:

  • ATT2: { (Q, T), (void*, void*) } . T是这里唯一的模板参数,它会发现T必须是QATT2 的类型推导很容易成功。

  • BTT1: { (Q, T), (void*, typename Const&lt;T&gt;::type*) } .这里会发现T 也是Qtypename Const&lt;T&gt;::type* 是一个未推断的上下文,因此它不会用于推断任何内容。


这是我的第一个问题:现在会使用为第一个参数推导出的T 的值吗?如果答案是否定的,那么第一个模板更专业。这不可能,因为 GCC 和 Comeau 都说第二个模板更专业,我不认为他们错了。所以我们假设“是”,并将void* 插入T。段落 (14.8.2.4) 说“对每一对独立进行推导,然后将结果组合起来” 以及 “但是,在某些情况下,该值不参与类型推导, 而是使用模板参数的值,这些值要么在别处推导出来,要么明确指定。” 这听起来也像“是”。

因此,对于每个 A / P 对,演绎也成功。现在,每个模板至少和另一个模板一样专业化,因为演绎也不依赖于任何隐式转换并且在两个方向上都成功了。因此,调用应该是模棱两可的。

所以我的第二个问题:现在,为什么实现说第二个模板更专业?我忽略了哪一点?


编辑:我在最近的 GCC 版本 (4.4) 中测试了显式特化和实例化,两者都告诉我对特化的引用是模棱两可的,而旧版本的 GCC (@ 987654372@) 不会产生歧义错误。这表明最近的 GCC 版本对函数模板的偏序不一致。

template<typename T>
struct Const { typedef void type; };

template<typename T>
void f(T, typename Const<T>::type*) { cout << "Const"; } // T1

template<typename T>
void f(T, void*) { cout << "void*"; } // T2

template<> void f(int, void*) { }
  // main.cpp:11: error: ambiguous template specialization 
  // 'f<>' for 'void f(int, void*)'

【问题讨论】:

    标签: c++ templates template-argument-deduction function-templates partial-ordering


    【解决方案1】:

    这是我的目标。我同意Charles Bailey 的错误步骤是从Const&lt;Q&gt;::Type* 转到void*

    template<typename T>
    void f(T, typename Const<T>::type*) { cout << "Const"; } // T1
    
    template<typename T>
    void f(T, void*) { cout << "void*"; } // T2
    

    我们要采取的步骤是:

    14.5.5.2/2

    给定两个重载的函数模板,可以通过依次转换每个模板并使用参数推导 (14.8.2) 将其与另一个进行比较来确定一个是否比另一个更专业。

    14.5.5.2/3-b1

    对于每个类型模板参数,合成一个唯一类型并将其替换为函数参数列表中该参数的每次出现,或返回类型中的模板转换函数。

    在我看来,类型合成如下:

    (Q, Const<Q>::Type*)    // Q1
    (Q, void*)              // Q2
    

    我没有看到任何要求T1 的第二个综合参数为void* 的措辞。我也不知道在其他情况下有任何先例。 Const&lt;Q&gt;::Type* 类型在 C++ 类型系统中是完全有效的类型。

    所以现在我们执行推演步骤:

    第二季度到第一季度

    我们尝试推导出 T1 的模板参数,所以我们有:

    • 参数1:T推导出为Q
    • 参数 2:非推断上下文

    即使参数 2 是非推导上下文,推导仍然成功,因为我们有 T 的值。

    第一季度到第二季度

    推导出 T2 的模板参数:

    • 参数1:T推导出为Q
    • 参数2:void*Const&lt;Q&gt;::Type*不匹配所以扣减失败。

    恕我直言,这就是标准让我们失望的地方。参数不依赖,因此不清楚应该发生什么,但是,我的经验(基于对 14.8.2.1/3 的眯眼阅读)是,即使参数类型 P 不依赖,那么参数类型 A 应该匹配它。

    T1 的综合参数可用于特化 T2,但反之则不行。因此,T2 比 T1 更专业,因此也是最好的函数。


    更新 1:

    只是为了掩盖关于Const&lt;Q&gt;::type 无效的观点。考虑以下示例:

    template<typename T>
    struct Const;
    
    template<typename T>
    void f(T, typename Const<T>::type*) // T1
    { typedef typename T::TYPE1 TYPE; }
    
    template<typename T>
    void f(T, void*)                    // T2
    { typedef typename T::TYPE2 TYPE ; }
    
    template<>
    struct Const <int>
    {
      typedef void type;
    };
    
    template<>
    struct Const <long>
    {
      typedef long type;
    };
    
    void bar ()
    {
      void * p = 0;
      f (0, p);
    }
    

    在上面,Const&lt;int&gt;::type 在我们执行通常的重载解决规则时使用,但在我们处理部分重载规则时不使用。为Const&lt;Q&gt;::type 选择任意专业化是不正确的。它可能不直观,但编译器很高兴拥有Const&lt;Q&gt;::type* 形式的合成类型并在类型推导期间使用它。


    更新 2

    template <typename T, int I>
    class Const
    {
    public:
      typedef typename Const<T, I-1>::type type;
    };
    
    template <typename T>
    class Const <T, 0>
    {
    public:
      typedef void type;
    };
    
    template<typename T, int I>
    void f(T (&)[I], typename Const<T, I>::type*)     // T1
    { typedef typename T::TYPE1 TYPE; }
    
    template<typename T, int I>
    void f(T (&)[I], void*)                           // T2
    { typedef typename T::TYPE2 TYPE ; }
    
    
    void bar ()
    {
      int array[10];
      void * p = 0;
      f (array, p);
    }
    

    Const 模板用某个值I 实例化时,它会递归地实例化自身,直到I 达到0。此时选择了部分特化Const&lt;T,0&gt;。如果我们有一个编译器为函数的参数合成一些真实类型,那么编译器将为数组索引选择什么值?说10?好吧,这对于上面的示例来说很好,但它与部分特化 Const&lt;T, 10 + 1&gt; 不匹配,至少从概念上讲,这将导致无限数量的主递归实例化。无论它选择什么值,我们都可以将结束条件修改为该值 + 1,然后我们将在偏序算法中有一个无限循环。

    我看不出偏序算法如何正确实例化 Const 以找到 type 的真正含义。

    【讨论】:

    • Const&lt;Q&gt;::Type*void*。只是另一种命名方式。如果是另一种类型,那么它是什么类型?所以Const&lt;Q&gt;::Type 是一个命名为void 的限定ID(在这种情况下),所以void*void* 完美匹配并使它们“兼容”。
    • @litb:我不同意。如果在 T 是 int 的地方有一个明确的 Const 特化怎么办?我们总是选择主模板是否正确?
    • 那么Const&lt;Q&gt;::Type* 将是int*。但在我们的例子中(在这种情况下:))它是void*
    • 看这个例子:template&lt;typename T&gt; struct A { typedef int type; };你说A&lt;bool&gt;::type不是int的类型。
    • @Richard,我刚刚检查了clang,它实际上并没有合成任何类型!它只是使用原始函数模板参数列表以及其中的依赖类型。所以它忽略了整个“一个唯一的类型、值或模板被替换......”部分。标准中的“唯一类型”和“唯一值”似乎分别与“依赖类型”和“类型依赖表达式”相同。不过,不确定“唯一模板”对应的是什么。但这将解释为什么Const&lt;Q&gt;::type被视为无效。
    【解决方案2】:

    编辑:在研究了他们的部分排序算法的Clang's 实现(Doug Gregor)之后,我开始同意其余的海报,即原始示例并非“有意”模棱两可 - 即使标准关于在这种情况下应该发生什么,还不是很清楚。我编辑了这篇文章以表明我修改后的想法(为了我自己的利益和参考)。特别是 Clang 的算法澄清了“typename Const&lt;T&gt;::type”在部分排序步骤中不会被转换为“void” - 并且每个 A/P 对都是相互独立地推导出来的。

    最初我想知道为什么以下内容被认为是模棱两可的:

            template<class T> void f(T,T*);  // 1
    
            template<class T> void f(T, int*); // 2
    
            f(0, (int*)0); // ambiguous
    

    (The above is ambiguous because one cannot deduce f1(U1,U1*) from f2(T,int*), and going the other way, one cannot deduce f2(U2,int*) from f1(T,T*). Neither is more specialized.)

    但以下内容不会有歧义:

            template<class T> struct X { typedef int type; };
            template<class T> void f(T, typename X<T>::type*); // 3
            template<class T> void f(T, int*); // 2
    

    (人们可能认为它模棱两可的原因是如果发生以下情况:
    - f3(U1,X&lt;U1&gt;::type*) -&gt; f3(U1, int*) ==&gt; f2(T,int*) (deduction ok, T=U1)
    - f2(U2,int*) ==&gt; f3(T, X&lt;T&gt;::type*) (deduction ok, T=U2 makes X&lt;U2&gt;::type* -&gt; int*)
    如果这是真的,那么任何一个都不会比另一个更专业。)

    在研究了 Clang 的部分排序算法之后,很明显他们将上面的 '3' 视为:

    template<class T, class S> void f(T, S*); // 4
    

    所以对 'typename X::type' 的一些独特的 'U' 的扣除将成功 -

    • f3(U1,X&lt;U1&gt;::type*) is treated as f3(U1, U2*) ==&gt; f2(T,int*) (deduction not ok)
    • f2(U2,int*) ==&gt; f3(T,S* [[X&lt;T&gt;::type*]]) (deduction ok, T=U2, S=int)

    所以“2”显然比“3”更专业。

    【讨论】:

    • 好点。我也不明白将X&lt;T&gt; 放在两者之间会有什么不同。
    【解决方案3】:

    T1 的转换参数列表(Q 插入):(Q,类型名 常量::类型*)。的类型 参数是 AT = (Q, void*)

    我想知道这是否真的是一个正确的简化。当您合成Q 类型时,是否允许为Const 指定一个特化来确定模板规范化的顺序?

    template <>
    struct Const<Q> { typedef int type; }
    

    这意味着T2 至少不像T1 那样专门化,因为void* 参数与T1 的任何给定模板参数的第二个参数都不匹配。

    【讨论】:

    • 类型“Q”是唯一的,由编译器合成,仅用于此目的(我认为这就是“唯一”的含义)并且没有名称。我们不能用它来定义那个专业。我也不确定我所做的简化是否有效。但是我们必须得到一个参数类型。因此,要查看typename Const&lt;Q&gt;::type 是什么类型,必须查看Const&lt;Q&gt;“T1 更专业,因为 void 参数不适用于 T1 的所有模板参数 T 的第二个参数。”*:但是 GCC 和 Comeau 不同意这一点 :( 他们说 T2 更专业,.. .
    • ......即使我把“Const”的专业化,比如“int”。
    • 你是对的;我错了。在确定 14.5.5.2/4 中的“至少作为专业化”时,我“允许”隐式覆盖从其他类型中无效*。不过,我仍然不确定它们中的任何一个至少和另一个一样专业。
    • @litb:你为什么觉得“我们必须得到一个参数类型”?我认为这可能是逻辑错误的来源。
    • @Richard,因为没有类型,我们无法进行任何类型推导。所以14.8.2.4/1“模板参数可以在几种不同的上下文中推导出来,但在每种情况下,都会将根据模板参数指定的类型(称为 P)与实际类型(称为 A)进行比较, ...”Const&lt;Q&gt;::type 只是 void 类型(简单类型说明符)的另一种语法(qualified-id)。 Const&lt;Q&gt;::type 也不依赖,所以这不可能是 “它不等于任何其他类型,因为它是依赖的”
    【解决方案4】:

    编辑:请忽略这篇文章 - 在研究了 Doug Gregor 实现的部分排序的 clangs 算法之后(尽管在撰写本文时它只是部分实现了 - 似乎与 OP 的问题相关的逻辑已经足够充分地实现了) - 它似乎将未推断的上下文视为另一个模板参数。这表明带有显式 void* 参数的重载应该是更专业的版本,并且不应该有歧义。像往常一样,科莫是正确的。 现在至于标准中明确定义这种行为的措辞 - 那是另一回事......

    由于这篇文章也发布在 comp.lang.c++.moderated 上,并且似乎在那里也引起了一些混乱 - 我想我也会在这里向那个小组发布我的答案 - 因为讨论显然与此处提出的问题。

    On Jul 25, 1:11 pm, Bart van Ingen Schenau &lt;b...@ingen.ddns.info&gt; wrote:

    You are going one step too fast here. How do you know (and would the compiler know) that there is no specialisation of Const&lt;Q&gt; such that Const&lt;Q&gt;::type != void?

    As far as I can see, the compiler would transform the parameter-list of A to: AT=(Q, &lt;unknown&gt;*). To call B with these parameters requires an implicit conversion (&lt;unknown&gt;* to void*) and therefore A is less specialised than B.

    我认为这是不正确的。在检查哪个功能更多时 专门的(在偏序期间),编译器将 参数列表到(Q, void*) - 即它实际上实例化了相关的 模板(最佳匹配)并在其中查找“类型”的值 - 在这种情况下,基于 在主模板上,它将是无效的*。

    关于您关于部分专业化的观点 - 检查时 哪个模板比另一个更专业,唯一可以使用的类型 是唯一生成的类型 - 如果此时有其他专业化 声明的实例化(在进行重载决议时) 他们将被考虑。如果您稍后添加它们,它们应该被选中 您将违反 ODR(根据 14.7.4.1)

    部分/显式专业化也将在期间得到考虑 候选集的形成——但这次使用实际参数的类型 到功能。如果(X 的)最佳匹配偏特化导致 对某些具有更好的隐式转换序列的函数类型 参数,那么我们永远不会进入偏序阶段,并且 将选择“更好”功能(在使其成为部分功能之前 订购阶段)

    以下是 cmets 的示例,说明各个步骤应执行的操作:

        template<class T, bool=true> struct X;  // Primary
    
        template<class T> struct X<T,true> { typedef T type; };  // A
        template<> struct X<int*,true> { typedef void* type; };  // B
    
    
        template<class T> void f(T,typename X<T>::type); //1
        template<class T> void f(T*,void*); //2
    
    
        int main()
        {
          void* pv;
          int* pi;
    
    
          f(pi,pi);   
          // two candidate functions: f1<int*>(int*,void*),  f2<int>(int*,void*)
          // Note: specialization 'B' used to arrive at void* in f1
          // neither has a better ICS than the other, so lets partially order
          // transformed f1 is f1<U1>(U1,X<U1,true>::type) --> f1<U1>(U1,U1) 
          //       (template 'A' used to get the second U1)
          // obviously deduction will fail (U1,U1) -> (T*,void*)
          // and also fails the other way (U2*, void*) -> (T,X<T>::type)
          // can not partially order them - so ambiguity 
    
    
    
    
          f(pv,pv);  
          // two candidate functions: f1<void*>(void*,void*), f2<void>(void*,void*)
          // Note: specialization 'A' used to arrive at second void* in f1
          // neither has a better ICS than the other, so lets partially order
          // transformed f1 is f1<U1>(U1,X<U1>::type) --> f1<U1>(U1,U1) 
          //       (template 'A' used to get the second U1)
          // obviously deduction will fail (U1,U1) -> (T*,void*)
          // and also fails the other way (U2*, void*) -> (T,X<T>::type)
          // can not partially order them - so ambiguity again             
    
        }
    

    还值得一提的是,如果主模板没有定义——那么 SFINAE 在部分排序阶段运行, 两者都不能从另一个推导出来,因此会产生歧义。

    此外,如果您添加另一个模板,如果其中任何一个函数的实例化点移动到翻译单元中的其他位置,则会导致另一个匹配,您显然会违反 ODR。

    On Jul 25, 1:11 pm, Bart van Ingen Schenau &lt;b...@ingen.ddns.info&gt; wrote:

    首先,更专业意味着这些类型更少 该模板可以通过重载决议来选择。 使用这个,偏序的规则可以总结为: 为 A 找到一个可以调用 A 但不能调用 B 的类型,或者重载 分辨率更喜欢调用A。如果可以找到该类型,则B更 比 A 更专业。

    这里没有参数。 但是根据目前的规则,OP的示例必须是 模棱两可。


    最后,这里是对 litb 提出的两个具体问题的明确、明确的答案:

    1) 现在是否会使用为第一个参数推导出的 T 值?
    是的 - 当然,它必须,它正在做模板参数推导 - 必须维护“链接”。

    2) 现在,为什么实现说第二个更专业?
    因为他们错了;)

    我希望这可以解决问题 - 如果还有什么不清楚的地方请告诉我:)

    编辑: litb 在他的评论中提出了一个很好的观点——也许是说主模板总是会得到 用于具有唯一生成类型的实例化的声明太强了。
    在某些情况下不会调用主模板。
    我得到的是,当发生部分排序时,一些独特的生成类型是 用于匹配最佳专业。你是对的,它不一定是主要模板。 我已经编辑了上述语言来做到这一点。 他还提出了一个关于在实例化点之后定义更好匹配模板的问题。 根据实例化点部分,这将违反 ODR。


    标准规定,一旦创建了 A/P 对(使用 temp.func.order 中描述的转换规则),它们就会使用模板参数推导 (temp.deduct) 相互推导 - 并且该部分处理非推导上下文的情况,实例化模板及其嵌套类型,实例化的触发点。 temp.point 部分处理 ODR 违规(无论翻译单元内的实例化点如何,偏序的含义都不应改变)。我仍然不确定混乱来自哪里? – Faisal Vali 1 小时前 [删除此评论]

    litb:“请注意,将 Q 放入 Const::type 以构建参数的步骤并未明确包含在 SFINAE 规则中。 SFINAE 规则使用参数推导,将 Q 放入函数模板函数参数列表的段落位于 14.5.5.2。'

    这里必须使用 SFINAE 规则 - 怎么可能不使用? 我觉得它已经足够暗示了——我不否认它可能更清楚,虽然我鼓励委员会澄清 这个 - 我认为不需要澄清它来充分解释你的例子。

    让我提供一种链接它们的方法。 从(14.8.2)开始: “当指定显式模板参数列表时,模板参数必须与 模板参数列表,并且必须产生如下所述的有效函数类型;否则类型扣除 失败”

    从 (14.5.5.2/3) “使用的转换是: — 对于每个类型模板参数,合成一个唯一类型并用它替换每个出现的 函数参数列表中的那个参数,或者对于模板转换函数,在返回类型中。"

    在我看来,上面的引用意味着一旦你为每个模板参数“创建”了唯一的生成类型,函数声明必须是 显式 隐式实例化,将唯一类型作为模板参数提供给我们的函数模板。如果这导致无效 函数类型,那么不仅是转换,更重要的是后续模板参数推导必须 部分排序函数失败。

    从 (14.5.5.2/4) "使用转换后的函数参数列表,对另一个函数模板进行参数推导。转换后的模板 至少与其他一样特化,当且仅当,推导成功并且推导的参数类型 是完全匹配的(因此推导不依赖于隐式转换)。”

    如果转换后的函数参数列表导致替换失败,那么我们知道推导不可能成功。 而且由于演绎没有成功,它不像另一个那么专业——这就是我们需要知道的一切 对两者进行偏序。

    litb:我也不确定在这种情况下会发生什么:template&lt;typename T&gt; struct A; template&lt;typename T&gt; void f(T, typename A&lt;T&gt;::type); template&lt;typename T&gt; void f(T*, typename A&lt;T&gt;::type); 当然, 这本来是有效的代码,但是执行 A::type,它将失败,因为在 模板定义上下文,A 尚未定义” 另请注意,没有为由此产生的模板实例定义 POI 尝试确定排序时的一种替换(部分排序不依赖于 在任何情况下。它是所涉及的两个函数模板的静态属性)。 我认为这看起来像是标准中需要修复的问题。

    好的 - 我想我看到了我们在哪里看到的东西不同。如果我理解正确,你是说 当这些函数模板被声明时,编译器会跟踪它们之间的偏序, 无论重载决议是否被触发以在它们之间进行选择。 如果这就是您解释它的方式,那么我可以理解为什么您会期望您描述的上述行为。 但我不认为该标准曾经要求或强制要求这样做。

    现在,标准很清楚,部分排序与调用函数时使用的类型无关(我相信 当您将其描述为静态属性并且与上下文无关时,这就是您所指的)。

    标准也很明确,它只关心函数模板之间的偏序(调用偏序) 在重载解决(13.3.3/1)的过程中,当且仅当它不能根据 ICS 选择更好的功能或 如果一个是模板,另一个不是。 [类模板部分特化的部分排序是一个单独的问题 在我看来,使用需要实例化该特定类的相关上下文(其他模板定义)。]

    因此,在我看来,因为函数模板的部分排序机制在重载时被调用 执行解析,它必须使用可用的上下文的相关部分(模板定义和专业化) 在重载解决方案完成时。

    因此,根据我的解释,根据您上面使用“模板结构 A”的示例,代码是有效的。 部分排序不是在定义上下文中完成的。但是如果/当你碰巧调用重载决议 通过编写对 f((int*)0,0) 的调用在两个函数之间进行调用 - 并且当时编译器要么 尝试组装候选声明或对它们进行部分排序(如果进入部分排序步骤) 如果一个无效的表达式或类型作为函数类型的一部分产生,SFINAE 会帮助我们并告诉我们 我们认为模板推导失败(就偏序而言,这意味着一个 如果我们甚至不能转换模板,就不能比另一个更专业)。

    现在关于 POI - 如果您像我一样确信转换后的函数类型应该 使用显式提供的模板参数列表(使用唯一生成的类型)表示隐式实例化 那么以下标准引号是相关的:

    14.6.4.1/1 对于函数模板特化、成员函数模板特化或 类模板的成员函数或静态数据成员,如果特化是隐式实例化的 因为它是从另一个模板专业化和引用它的上下文中引用的 取决于模板参数,特化的实例化点就是实例化点 封闭的专业化。

    我的解释是转换函数类型和原始函数类型的 POI 是 与实际函数调用创建的那些函数的 POI 相同。

    litb: 因为偏序只是 a property of the syntactic form of parameters (i.e "T*" against "T(*)[N]"), 我会投票支持修改规范(比如“如果 Q 出现在 一个命名类型的限定ID,那么命名的类型是“Q”) 或者说命名的类型是另一种独特的类型。 This means that in template&lt;typename T&gt; void f(T, typename Const&lt;T&gt;::type*); the argument list is (Q, R*), for example. Same for template&lt;typename T&gt; void f(T*, typename ConstI&lt;sizeof(T)&gt;::type); the arg lisst would be (Q*, R). A similar rule would be needed for non-type parameters, of course. 不过,我必须考虑一下并制作一些测试用例,看看这是否会产生自然排序。

    啊 - 现在您正在建议一种可能的解决方案,以解决歧义,有利于我们 所有人都直观地期待 - 这是一个单独的问题,虽然我喜欢你前进的方向, 和你一样,在宣布它的可操作性之前,我也必须考虑一下。

    感谢您继续讨论。我希望 SO 不只是限制您放置 cmets。

    由于您可以编辑我的帖子,如果方便,请随时在帖子中回复。

    【讨论】:

    • 为什么它总是使用主模板?你有这个标准的报价吗?考虑:template&lt;typename T, bool = true&gt; struct X; template&lt;typename T&gt; struct X&lt;T, true&gt; { typedef void *type; }; 在这种情况下,部分特化匹配并将用于Q。我认为 Bat van Ingen 所追求的是,​​当函数模板的定义之后有另一个专门化时会发生什么。部分排序不能考虑它,因为它还不知道它。但以后对专业化的引用会考虑它。
    • 但我没有看到标准说明在这种情况下会发生什么。 :(
    • 标准规定,一旦创建了 A/P 对(使用 temp.func.order 中描述的转换规则),它们就会使用模板参数推导(temp.deduct)相互推导- 该部分处理非推导上下文的情况,实例化模板及其嵌套类型,触发实例化点。 temp.point 部分处理 ODR 违规(无论翻译单元内的实例化点如何,偏序的含义都不应改变)。我仍然不确定混乱来自哪里?
    • 请注意,将Q 放入Const&lt;T&gt;::type 以构建参数的步骤并未明确包含在 SFINAE 规则中。 SFINAE 规则使用参数推导,将Q 放入函数模板函数参数列表的段落位于 14.5.5.2。我也不确定在这种情况下会发生什么:template&lt;typename T&gt; struct A; template&lt;typename T&gt; void f(T, typename A&lt;T&gt;::type); template&lt;typename T&gt; void f(T*, typename A&lt;T&gt;::type); 当然,这应该是有效的代码,但是执行A&lt;Q&gt;::type,它将失败,因为在模板定义上下文中,A 尚未定义跨度>
    • 另请注意,在尝试确定排序时,没有为由这种替换产生的模板实例定义 POI(部分排序不依赖于任何上下文。它是所涉及的两个函数模板的静态属性)。我认为这看起来像是标准中的一个问题,需要修复。由于部分排序只是参数语法形式的一个属性(即“T*”对“T(*)[N]”),我会投票支持修改规范(比如“如果 Q 出现在嵌套名称说明符中的限定ID命名类型,那么命名的类型是“Q”)
    猜你喜欢
    • 1970-01-01
    • 2012-04-18
    • 2015-02-18
    • 2016-01-30
    • 2017-04-24
    • 2015-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多