在阅读另一个问题时,我遇到了部分排序的问题,我将其缩减为以下测试用例
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.1
或AT
本身(比如删除引用声明符等),直接转到BT
,每个都独立14.8.2.4
/ A
对会进行类型扣除:
P
针对AT
: T2
{
(Q, T)
{{1 } ,
。 (void*, void*)
是此处唯一的模板参数,它会发现}
必须是T
。 T
针对Q
,类型扣除成功。
AT
针对T2
: BT
T1
{
{{1 } (Q, T)
。它会发现,
也是(void*, typename Const<T>::type*)
。 }
是一个未推断的上下文,因此不会用于推断任何内容。
这是我的第一个问题:现在这会使用第一个参数推导出的T
的值吗?如果答案为否,则第一个模板更专业。情况并非如此,因为GCC和Comeau都说第二个模板更专业,我不相信它们是错的。所以我们假设“是”,并将Q
插入typename Const<T>::type*
。段落(T
)说“扣除是针对每一对独立完成的,然后结果合并”并且“但在某些情况下,该值不参与类型推导,而是使用模板参数的值,这些参数可以在别处推导或明确指定。“这听起来像是”是“。
所以我的第二个问题:现在,为什么实现说第二个模板更专业?我忽略了什么?
编辑:我测试了显式特化和实例化,在最近的GCC版本(void*
)中,两者都告诉我,对专业化的引用是模糊的,而旧版本的GCC (T
)不会引起歧义错误。这表明最近的GCC版本对功能模板的部分排序不一致。
14.8.2.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的模板参数,因此我们有:
T
推断为Q
即使参数2是非推导的上下文,但扣除仍然成功,因为我们有一个T的值。
Q1到T2
推导T2的模板参数我们有:
T
推断为Q
void*
与Const<Q>::Type*
不匹配,因此扣减失败。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)