具有未受限上下文的函数模板的部分排序

时间:2009-07-24 21:45:32

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

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

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)现在说第二个模板更专业。但为什么呢?

让我通过部分订购并展示我有问题的地方。根据{{​​1}},Q可以是用于确定部分排序的唯一组合类型。

  • 14.5.5.2(已插入Q)的已转换参数列表:T1。参数的类型为(Q, typename Const<Q>::type*) = AT
  • (Q, void*)(已插入Q)的已转换参数列表:T2 = BT,它们也是参数的类型。
  • (Q, void*)的未转换参数列表:T1
  • (T, typename Const<T>::type*)的未转换参数列表:T2

由于C ++ 03指定了这个,我确实使用了我在几个缺陷报告中读到的意图。 (T, void*)的上述转换参数列表(由我称为T1)用作AT “从函数调用”中推导模板参数的参数列表。

14.8.2.1不再需要转换14.8.2.1AT本身(比如删除引用声明符等),直接转到BT,每个都独立14.8.2.4 / A对会进行类型扣除:

  • P针对AT T2 { (Q, T) {{1 } , (void*, void*)是此处唯一的模板参数,它会发现}必须是TT针对Q,类型扣除成功。

  • AT针对T2 BT T1 { {{1 } (Q, T) 。它会发现,也是(void*, typename Const<T>::type*)}是一个未推断的上下文,因此不会用于推断任何内容。


这是我的第一个问题:现在这会使用第一个参数推导出的T的值吗?如果答案为否,则第一个模板更专业。情况并非如此,因为GCC和Comeau都说第二个模板更专业,我不相信它们是错的。所以我们假设“是”,并将Q插入typename Const<T>::type*。段落(T)说“扣除是针对每一对独立完成的,然后结果合并”并且“但在某些情况下,该值不参与类型推导,而是使用模板参数的值,这些参数可以在别处推导或明确指定。“这听起来像是”是“。

因此,对于每个A / P对,扣除也是成功的。现在,每个模板至少与另一个模板一样专业,因为演绎不依赖于任何隐式转换并且在两个方向上都成功。结果,呼叫应该是模糊的。

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


编辑:我测试了显式特化和实例化,在最近的GCC版本(void*)中,两者都告诉我,对专业化的引用是模糊的,而旧版本的GCC (T)不会引起歧义错误。这表明最近的GCC版本对功能模板的部分排序不一致。

14.8.2.4

4 个答案:

答案 0 :(得分:6)

这是我的目标。我同意Charles Bailey不正确的步骤是从Const<Q>::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<Q>::Type*在C ++类型系统中是完全有效的类型。

现在我们执行扣除步骤:

Q2到T1

我们尝试推断T1的模板参数,因此我们有:

  • 参数1:T推断为Q
  • 参数2:Nondeduced context

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

Q1到T2

推导T2的模板参数我们有:

  • 参数1:T推断为Q
  • 参数2:void*Const<Q>::Type*不匹配,因此扣减失败。
恕我直言,这是标准让我们失望的地方。该参数不依赖,因此不太清楚应该发生什么,但是,我的经验(基于14.8.2.1/3的斜视读数)是即使参数类型P不依赖,那么参数类型A应该匹配它

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


更新1:

只是为了掩盖关于Const<Q>::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<int>::type,但是当我们得到部分重载规则时则不使用Const<Q>::type。为Const<Q>::type*选择任意专门化是不正确的。它可能不直观,但编译器非常乐意使用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); } 形式的合成类型并在类型推导过程中使用它。


更新2

Const

I模板使用某个值I进行实例化时,它会递归实例化,直到Const<T,0>达到0.这是选择部分特化Const<T, 10 + 1>的时候。如果我们有一个编译器为函数的参数合成一些实数类型,那么编译器会为数组索引选择什么值?说10?好吧,这对于上面的例子来说没问题,但是它与部分特化Const不匹配,从概念上讲,它至少会导致主要的无限数量的递归实例化。无论选择什么值,我们都可以将结束条件修改为值+ 1,然后我们在部分排序算法中有一个无限循环。

我没有看到部分排序算法如何正确地实例化type以找到{{1}}实际上是什么。

答案 1 :(得分:2)

编辑:在研究了Clang's实施(Doug Gregor)他们的部分排序算法之后,我已经同意其余的海报,原始的例子并非“意图”含糊不清 - 尽管标准并不像在这种情况下应该发生什么那样清楚。我已编辑此帖以表明我的修改过的想法(为了我自己的利益和参考)。特别是Clang的算法澄清了在部分排序步骤中'typename Const<T>::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<U1>::type*) -> f3(U1, int*) ==> f2(T,int*) (deduction ok, T=U1)
  - f2(U2,int*) ==> f3(T, X<T>::type*) (deduction ok, T=U2 makes X<U2>::type* -> int*)
如果这是真的,那么两个人都不会比另一个更专业。)

在研究了Clang的偏序算法之后,很明显他们将上面的'3'看作是:

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

因此,对'typename X :: type'扣除一些独特的'U'将成功 -

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

因此'2'显然比'3'更专业。

答案 2 :(得分:1)

  

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

我想知道这是否真的是一个正确的简化。当您合成类型Q时,为了确定模板规范的排序,您是否可以为Const设想一个专门化?

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

这意味着T2至少与T1不同,因为void*参数与T1的任何给定模板参数的第二个参数不匹配。

答案 3 :(得分:1)