使用具有未减少上下文的函数模板的部分排序 [英] Partial ordering with function template having undeduced context

查看:167
本文介绍了使用具有未减少上下文的函数模板的部分排序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在阅读另一个问题时,我遇到了一个部分顺序问题,我转向下面的测试用例

  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在f(0,0)上扼流(无法与T1匹配)
void * p = 0 ;
f(0,p);
}

对于两个函数模板,输入重载分辨率的特殊化函数类型为 void(int,void *)。但部分订购(根据gotau和GCC)现在说,第二个模板更专业。但为什么?



让我经历部分订购,并显示我有问题。可以 14.5.5.2 ,用于确定部分排序的唯一化妆类型。




  • 转换参数列表 T1 (插入Q): ,typename Const< Q> :: type *)。参数的类型为 AT = (Q,void *)

  • T2的转换参数列表(插入Q): BT = *),这也是参数的类型。

  • T1 的非转换参数列表:(T,typename Const< T& *)

  • T2未变换的参数列表 ,void *)



由于C ++ 03低估了这一点,我读了几个缺陷报告。上面转换的 T1 的参数列表(由我调用 AT )用作 14.8.2.1 从函数调用中导入模板参数



14.8.2.1 不需要转换 AT BT 本身(如删除引用声明符等),并直接进入 14.8.2.4 对于每个 A / P 对类型扣除:




  • AT T2 { (Q,T) (void *,void *) } T 是这里唯一的模板参数,它会发现 T 必须是 / code>。类型扣除对 AT T2 成功。


  • BT T1 { (Q,T) (void *,typename Const< T> :: type *) } 。在这里也会发现 T Q typename Const< T> :: type * 是一个未推导的上下文,因此它不会用于推导任何东西。







问题:现在是否会使用为第一个参数推导的 T 的值?如果答案是否定的,那么第一个模板是更专门的。这不可能是这样,因为GCC和Comeau都说第二个模板更专业,我不相信他们错了。因此,我们假设是,并将 void * 插入到 T 。段落( 14.8.2.4 )表示对每个对独立完成扣除,然后将结果合并 但是,某些上下文,该值不参与类型推导,而是使用在其他地方或明确指定的模板参数的值。这听起来像是。



因此,每个A / P对的扣除也成功。现在,每个模板至少与其他模板一样专业,因为推导不依赖于任何隐式转换并且在两个方向上都成功。因此,调用应该是模糊的。



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



编辑:I测试显式专门化和实例化,并且在最近的GCC版本( 4.4 )中告诉我对特化的引用是不明确的,而旧版本的GCC( 4.1 )不会引起歧义错误。这表明最近的GCC版本具有不一致的功能模板的部分排序。

 模板< 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&


解决方案

我同意 Charles Bailey 错误的步骤是从 Const< Q> :: Type * 转到 void *

 模板< 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 be void * 。我不知道在其他情况下有什么先例。类型 Const< Q> :: Type * 在C ++类型系统中是完全有效的类型。



我们执行扣除步骤:



Q2到T1



T1的模板参数,因此我们有:




  • 参数1: T Q

  • 参数2:未保护的上下文



尽管参数2是非推导的上下文,但是扣除仍然成功,因为我们有T的值。



Q1到T2



删除T2的模板参数我们有:




  • 参数1 : T 被推导为 Q

  • 参数2: void * 不匹配 Const< Q> :: Type * ,因此导致失败。



    • IMHO,这里的标准让我们失望。参数不依赖,所以它不是很清楚应该发生什么,但是,我的经验(基于14.8.2.1/3的斜视读)是,即使参数类型P不依赖,那么参数类型A应该匹配它。



      T1的合成参数可以用于专门化T2,反之亦然。因此,T2比T1更专业,所以是最好的功能。






      UPDATE 1:



      只是为了覆盖关于 Const< Q> :: type 为void的主题。考虑下面的例子:

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

      模板<>
      struct Const< long>
      {
      typedef long type;
      };

      void bar()
      {
      void * p = 0;
      f(0,p);
      }

      在上面, Const< int>类型用于执行通常的重载解析规则,而不是当我们得到部分重载规则时。为 Const< Q> :: type 选择任意专业化是不正确的。它可能不直观,但是编译器很高兴有一种合成类型的形式 Const< Q> :: type * 并且在类型推导期间使用它。 / p>




      UPDATE 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 :: 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< T,0> 。如果我们有一个编译器为函数的参数合成一些真正的类型,那么编译器为数组索引选择什么值?说10?嗯,对于上面的例子这将是罚款,但是它不会匹配部分专门化 Const ,其在概念上至少将导致无限数量的主要的递归实例化。无论选择什么值,我们都可以将结束条件修改为该值+ 1,然后在部分排序算法中有一个无限循环。



      没有看到部分排序算法如何正确实例化 Const 以找到类型真的是。


      While reading another question, i came to a problem with partial ordering, which i cut down to the following test-case

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

      For both function templates, the function type of the specialization that enters overload resolution is void(int, void*). But partial ordering (according to comeau and GCC) now says that the second template is more specialized. But why?

      Let me go through partial ordering and show where i have questions. May Q be an unique made-up type used for determining partial ordering according to 14.5.5.2.

      • Transformed parameter-list for T1 (Q inserted): (Q, typename Const<Q>::type*). The types of the arguments are AT = (Q, void*)
      • Transformed parameter-list for T2 (Q inserted): BT = (Q, void*), which are also the types of the arguments.
      • Non-transformed parameter-list for T1: (T, typename Const<T>::type*)
      • Non-transformed parameter-list for T2: (T, void*)

      Since C++03 under-specifies this, i did use the intention that i read about in several defect reports. The above transformed parameter list for T1 (called AT by me) is used as argument list for 14.8.2.1 "Deducing template arguments from a function call".

      14.8.2.1 does not need to transform AT or BT itself anymore (like, removing reference declarators, etc), and goes straight to 14.8.2.4, which independently for each A / P pair does type deduction:

      • AT against T2: { (Q, T), (void*, void*) }. T is the only template parameter here, and it will find that T must be Q. Type deduction succeeds trivially for AT against T2.

      • BT against T1: { (Q, T), (void*, typename Const<T>::type*) }. It will find that T is Q, too here. typename Const<T>::type* is an un-deduced context, and so it won't be used to deduce anything.


      Here is my first question: Will this now use the value of T deduced for the first parameter? If the answer is no, then the first template is more specialized. This can't be the case, because both GCC and Comeau say that the second template is more specialized, and i don't believe they are wrong. So we assume "yes", and insert void* into T. The paragraph (14.8.2.4) says "Deduction is done independently for each pair and the results are then combined" and also "In certain contexts, however, the value does not participate in type deduction, but instead uses the values of template arguments that were either deduced elsewhere or explicitly specified." This sounds like "yes" too.

      Deduction therefore succeeds too, for every A / P pair. Now, each template is at least as specialized as the other, because deduction didn't also rely on any implicit conversions and succeeded in both directions. As a result, the call should be ambiguous.

      So my second question: Now, why do the implementations say that the second template is more specialized? What point did i overlook?


      Edit: I tested explicit specialization and instantiation, and both, in recent GCC versions (4.4) tell me that the reference to the specialization is ambiguous, while an older version of GCC (4.1) doesn't rise that ambiguity error. This suggests that recent GCC versions have inconsistent partial ordering for function templates.

      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*)'
      

      解决方案

      Here's my go at this. I agree with Charles Bailey that the incorrect step is to go from Const<Q>::Type* to void*

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

      The steps we want to take are:

      14.5.5.2/2

      Given two overloaded function templates, whether one is more specialized than another can be determined by transforming each template in turn and using argument deduction (14.8.2) to compare it to the other.

      14.5.5.2/3-b1

      For each type template parameter, synthesize a unique type and substitute that for each occurrence of that parameter in the function parameter list, or for a template conversion function, in the return type.

      In my opinion, the types are synthesized as follows:

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

      I don't see any wording that requires that the second synthesized parameter of T1 be void*. I don't know of any precedent for that in other contexts either. The type Const<Q>::Type* is perfectly valid type within the C++ type system.

      So now we perform the deduction steps:

      Q2 to T1

      We try to deduce the template parameters for T1 so we have:

      • Parameter 1: T is deduced to be Q
      • Parameter 2: Nondeduced context

      Even though parameter 2 is a non deduced context, deduction has still succeeded because we have a value for T.

      Q1 to T2

      Deducing the template parameters for T2 we have:

      • Parameter 1: T is deduced to be Q
      • Parameter 2: void* does not match Const<Q>::Type* so deduction failure.

      IMHO, here's where the standard lets us down. The parameter is not dependent so it's not really clear what should happen, however, my experience (based on a squinted read of 14.8.2.1/3) is that even where the parameter type P is not dependent, then the argument type A should match it.

      The synthesized arguments of T1 can be used to specialize T2, but not vice versa. T2 is therefore more specialized than T1 and so is the best function.


      UPDATE 1:

      Just to cover the poing about Const<Q>::type being void. Consider the following example:

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

      In the above, Const<int>::type is used when we're performing the usual overload resolution rules, but not when we get to the partial overloading rules. It would not be correct to choose an arbitrary specialization for Const<Q>::type. It may not be intuitive, but the compiler is quite happy to have a synthasized type of the form Const<Q>::type* and to use it during type deduction.


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

      When the Const template is instantiated with some value I, it recursively instantiates itself until I reaches 0. This is when the partial specialization Const<T,0> is selected. If we have a compiler which synthesizes some real type for the parameters of the function, then what value will the compiler choose for the array index? Say 10? Well, this would be fine for the above example but it wouldn't match the partial specialization Const<T, 10 + 1> which, conceptually at least, would result in an infinite number of recursive instantiations of the primary. Whatever value that it selected we could modify the end condition to be that value + 1, and then we'd have an infinite loop in the partial ordering algorithm.

      I do not see how the partial ordering algorithm could correctly instantiate Const to find what type really is.

      这篇关于使用具有未减少上下文的函数模板的部分排序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆