假设您有两个结构,Generic_A
和Generic_B
。 Generic_B
源自Generic_A
。为什么当Generic_B
尝试访问其父级Generic_A
中的方法时,会产生以下错误:
test2.cpp: In function 'int main()':
test2.cpp:26: error: no matching function for call to 'case1(void (Generic_A::*)()'
此代码使用gcc版本4.4.6编译,复制了问题:
#include <stdio.h>
struct Generic_A
{
void p1() { printf("%s\n", __PRETTY_FUNCTION__); };
};
struct Generic_B : public Generic_A
{
void p2() { printf("%s\n", __PRETTY_FUNCTION__); };
};
template <class T,class... ARGS>
void case1( void (T::*p)(ARGS...) ) {
printf("%s\n", __PRETTY_FUNCTION__);
}
template <class T>
void case2( void (T::*p)() ) {
printf("%s\n", __PRETTY_FUNCTION__);
}
main()
{
//generates error
case1<Generic_B>(&Generic_B::p1);
//compiles fine
case2<Generic_B>(&Generic_B::p1);
}
两个函数调用之间唯一明显的区别是case1()
具有模板参数参数,case2()
没有。它们是否应该允许您将函数指针传递给Generic_B的父级中的方法(即&Generic_B::p1
)?
此外,在case1
中转换函数指针似乎有时会解决错误:
case1<Generic_B>( (void (Generic_B::*)()) &Generic_B::p1);
发生了什么事?
答案 0 :(得分:1)
这很棘手,但事实证明g ++是正确的。
首先,表达式&Generic_B::p1
的类型是void (Generic_A::*)()
。编译器使用Generic_B::
限定其名称查找并找到Generic_A
的成员。表达式类型取决于找到的成员的定义,而不是 qualified-id 中使用的类型。
但拥有
也是合法的void (Generic_B::*member)() = &Generic_B::p1;
因为存在从void (Generic_A::*)()
到void (Generic_B::*)()
的隐式转换。
每当函数模板用作函数调用时,编译器都会经历三个基本步骤(或尝试):
在函数声明中替换模板参数的任何显式模板参数。
对于仍然涉及至少一个模板参数的每个函数参数,将相应的函数参数与该函数参数进行比较,以(可能)推导出这些模板参数。
将推导出的模板参数替换为函数声明。
在这种情况下,我们有函数模板声明
template <class T,class... ARGS>
void case1( void (T::*p)(ARGS...) );
其中模板参数为T
和ARGS
,以及函数调用表达式
case1<Generic_B>(&Generic_B::p1)
其中显式模板参数为Generic_B
,函数参数为&Generic_B::p1
。
步骤1,替换显式模板参数:
void case1( void (Generic_B::*p)(ARGS...) );
步骤2,比较参数类型和参数类型:
参数类型(标准部分14.8.2中的P
)为void (Generic_B::*)(ARGS...)
。参数类型(A
)为void (Generic_A::*)()
。
C ++标准(N3485)14.8.2.1p4:
通常,演绎过程会尝试查找模板参数值,这些参数值会使推导的
A
与A
相同(在A
类型转换后如上所述)。但是,有三种情况可以产生差异:
如果原始
P
是引用类型,则推导出的A
(即引用所引用的类型)可以比转换后的{{1}更符合cv标准。 }。转换后的
A
可以是另一个指向成员类型的指针或指针,可以通过资格转换(4.4)转换为推导出的A
。如果
A
是一个类而P
的格式为 simple-template-id ,则转换后的P
可以是派生类推导出的A
。同样,如果A
是指向 simple-template-id 形式的类的指针,则转换后的P
可以是指向推导出的派生类的指针。A
。
因此,类型推导允许涉及A
/ const
和/或派生到基础转换的某些隐式转换,但不会考虑对成员指针的隐式转换。
在volatile
示例中,类型演绎失败,且函数不匹配。
不幸的是,没有办法明确指定模板参数包case1
应该用空列表替换。正如您已经发现的那样,您可以通过显式执行成员函数转换的必要指针来实现此工作,即使它作为隐式转换有效。