Joh*_*itb 22 c++ templates partial-ordering function-templates template-argument-deduction
在阅读另一个问题时,我遇到了部分排序问题,我将其缩减为以下测试用例
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);
}
Run Code Online (Sandbox Code Playgroud)
对于两个函数模板,进入重载分辨率的特化的函数类型是void(int, void*).但是,部分排序(根据comeau和GCC)现在说第二个模板更专业.但为什么?
让我通过部分排序,并显示我有问题的地方.可以Q被用于确定根据偏序的独特由上型14.5.5.2.
T1(Q插入)(Q, typename Const<Q>::type*).参数的类型是AT=(Q, void*)T2(Q inserted):BT= (Q, void*),它们也是参数的类型.T1:(T, typename Const<T>::type*)T2:(T, 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*) }TTQATT2
BT反对T1:.它会发现是,太在这里.是一个未推断的上下文,所以它不会被用来推断任何东西.{ (Q, T), (void*, typename Const<T>::type*) }TQtypename Const<T>::type*
这是我的第一个问题:这现在是否会使用T第一个参数的推导值?如果答案为否,则第一个模板更专业.情况并非如此,因为GCC和Comeau都说第二个模板更专业,我不相信它们是错的.因此,我们认为"是",并插入void*到T.段落(14.8.2.4)表示"为每一对独立完成扣除,然后将结果合并",并且"在某些上下文中,该值不参与类型推导,而是使用推导出的模板参数的值别的或明确指定的." 这听起来像是"是".
因此,对于每个A/P对,扣除也成功.现在,每个模板至少与另一个模板一样专业,因为演绎不依赖于任何隐式转换并且在两个方向上都成功.结果,呼叫应该是模糊的.
所以我的第二个问题:现在,为什么实现说第二个模板更专业?我忽略了什么?
编辑:我测试了显式的特化和实例化,并且在最近的GCC版本(4.4)中告诉我,对特化的引用是模糊的,而较旧版本的GCC(4.1)不会引起这种模糊性错误.这表明最近的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*)'
Run Code Online (Sandbox Code Playgroud)
这是我的目标.我同意查尔斯·贝利是不正确的步骤是,从去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
Run Code Online (Sandbox Code Playgroud)
我们要采取的步骤是:
14.5.5.2/2
给定两个重载的函数模板,可以通过依次转换每个模板并使用参数推导(14.8.2)将其与另一个进行比较来确定一个是否比另一个更加专业化.
14.5.5.2/3-b1
对于每个类型模板参数,合成唯一类型,并在函数参数列表中替换该参数的每次出现,或者在返回类型中替换模板转换函数.
在我看来,类型合成如下:
(Q, Const<Q>::Type*) // Q1
(Q, void*) // Q2
Run Code Online (Sandbox Code Playgroud)
我没有看到需要的第二合成参数的任何措辞T1是void*.在其他情况下,我也不知道有任何先例.该类型Const<Q>::Type*在C++类型系统中是完全有效的类型.
所以现在我们执行扣除步骤:
Q2到T1
我们尝试推导T1的模板参数,因此我们有:
T推断为Q尽管参数2是非推导的上下文,但扣除仍然成功,因为我们有一个T的值.
Q1到T2
推导T2的模板参数我们有:
T推断为Qvoid*不匹配,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);
}
Run Code Online (Sandbox Code Playgroud)
在上面,Const<int>::type当我们执行通常的重载决策规则时使用,但是当我们得到部分重载规则时则不使用.选择任意专门化是不正确的Const<Q>::type.它可能不直观,但编译器非常乐意拥有表单的合成类型Const<Q>::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);
}
Run Code Online (Sandbox Code Playgroud)
当Const模板使用某个值进行实例化时I,它会递归实例化自身,直到I达到0.这Const<T,0>是选择部分特化时的情况.如果我们有一个编译器为函数的参数合成一些实数类型,那么编译器会为数组索引选择什么值?说10?好吧,这对于上面的例子来说没问题,但它不会与部分特化相匹配Const<T, 10 + 1>,从概念上讲,它至少会导致主要的无限数量的递归实例化.无论选择什么值,我们都可以将结束条件修改为值+ 1,然后我们在偏序算法中有一个无限循环.
我没有看到部分排序算法如何正确地实例化Const以找到type真正的东西.