如果
P
是一个类,并且P
的形式为simple-template-id,则转换后的A
可以是推导的{{ 1}}。
这句话描述了如果函数模板参数是从“推导的D
派生出来的,那么它如何仍然有效,但是,对于“推导的A
”实际上是什么没有一个明确的定义。>
我的理论是,推论出的A
是原始的A
,其中替换了A
中的模板参数,但这会破坏试图找到使{{ 1}}并推导P
相同,因为在某些情况下,A是非参考,而推导A
是参考。
答案 0 :(得分:3)
推导函数模板参数的目的是弄清楚在使用模板名称(如函数名称)的地方应使用函数模板的哪个特殊化。例如,给定功能模板
template <typename T>
void f(T* value) {}
当您有类似的函数调用时
int* a = &x;
f(a);
这里的名称f
实际上不是函数的名称,而是函数模板的名称。编译器必须根据函数调用中给定的参数类型,来确定该调用实际上应该调用哪个具体的函数模板。换句话说,必须弄清楚应该为模板参数X
使用哪个模板参数T
才能到达可以在此处调用的实际函数f<X>
。与普通函数调用相比,这是一个反问题。现在不必使参数列表适合给定的签名(通过应用转换),我们现在必须使签名适合给定的参数列表。另一种看待它的方法是尝试推导模板参数,这些参数会使每个函数参数的类型与每个函数调用参数的类型匹配。这是[temp.deduct.call]/4在这里谈论的内容:
通常,推导过程会尝试查找模板参数值,以使推导的A与A相同
以上面的示例为例,给定一些推导模板参数X
,推导参数类型就是通过将推导X
替换为T
到函数参数类型{{1 }}(即此函数参数采用的参数类型)。如果我们推论T*
为X
,则将int
的{{1}}替换为int
会使我们推论的参数类型为T
。由于推导的参数类型T*
与实际参数的类型相同,因此我们发现函数int*
是我们想要的。
要使所有这些与正常函数调用的行为保持一致,请注意一些特殊情况。特别是对于数组和函数类型的函数调用参数,通常我们具有数组到指针和函数到指针的衰减以及顶级const。为了解决这个问题,该标准指定我们尝试匹配的参数类型int*
不仅被简单地视为相应函数调用参数的类型,而是首先被 transformed 应用数组到指针,函数到指针等的转换。经过转换的 f<int>
是我们实际上试图使推论的参数类型匹配的A
。这仅仅是为了解释为什么标准在此谈论“已转换的A
”。对于手头的问题,它并不是那么重要。转换后的A
只是我们实际上试图匹配的函数参数类型。
现在,我们有一些
A
和一些派生类
A
当您拥有类似的功能模板时
template <typename T> class B {};
和这样的函数调用
class D : public B<int> {};
没有模板参数template <typename T>
void f(const B<T>*) {}
,您可以选择D d;
f(&d);
,以使推导的参数类型X
等于T
。但是,由于const B<X>*
是从D*
派生的,因此将模板参数推论为D
仍然会导致函数专业化B<int>
可以接受该调用。 [temp.deduct.call]/4.3整个段落,尤其是您问题中的句子
如果
int
是一个类,并且f<int>
的形式是 simple-template-id ,则转换后的P
可以是派生类{{1 }}中的推论P
。
有没有让它确切地起作用……