无法通过可变参数函数将函数指针传递给父类中的方法--编译器错误?

can't pass function pointer to method in parent class through a variadic function--compiler bug?

本文关键字:函数 父类 方法 错误 编译器 变参 指针 参数      更新时间:2023-10-16

假设你有两个结构,Generic_AGeneric_BGeneric_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("%sn", __PRETTY_FUNCTION__); };
};
struct Generic_B : public Generic_A
{
    void p2() { printf("%sn", __PRETTY_FUNCTION__); };
};
template <class T,class... ARGS>
void case1( void (T::*p)(ARGS...) ) {
    printf("%sn", __PRETTY_FUNCTION__);
}
template <class T>
void case2( void (T::*p)() ) {
    printf("%sn", __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);

这是怎么回事?

这很棘手,但事实证明 g++ 是正确的。

首先,表达式&Generic_B::p1的类型是void (Generic_A::*)()。 编译器使用 Generic_B:: 来限定其名称查找并查找 Generic_A 的成员。 表达式类型取决于找到的成员的定义,而不是限定 id 中使用的类型。

但拥有也是合法的

void (Generic_B::*member)() = &Generic_B::p1;

因为存在从void (Generic_A::*)()void (Generic_B::*)()的隐式转换.

每当将函数

模板用作函数调用时,编译器都会经历三个基本步骤(或尝试(:

  1. 将任何显式模板参数替换为函数声明中的模板参数。

  2. 对于仍涉及至少一个模板参数的每个函数参数,将相应的函数参数与该函数参数进行比较,以(可能(推断出这些模板参数。

  3. 推导的模板参数替换为函数声明。

在本例中,我们有函数模板声明

template <class T,class... ARGS>
void case1( void (T::*p)(ARGS...) );

其中模板参数为 TARGS ,以及函数调用表达式

case1<Generic_B>(&Generic_B::p1)

其中显式模板参数Generic_B,函数参数&Generic_B::p1

所以第 1 步,替换显式模板参数:

void case1( void (Generic_B::*p)(ARGS...) );

步骤二,比较参数类型和参数类型:

参数类型(标准节 14.8.2 中的P(为 void (Generic_B::*)(ARGS...) 。 参数类型 (A( 是 void (Generic_A::*)()

C++标准 (N3485( 14.8.2.1p4:

通常,推

导过程会尝试查找模板参数值,以使推导的AA相同(在如上所述转换类型A之后(。 但是,有三种情况允许差异:

  • 如果原始P是参考类型,则推导的A(即参考所指的类型(可能比转换后的A更符合 cv 资格。

  • 转换后的A可以是另一个指针或指向成员类型的指针,可以通过限定转换 (4.4( 将其转换为推导的A

  • 如果P是一个类,并且P具有简单模板 id 的形式,则转换后的A可以是推导A的派生类。 同样,如果P是指向 simple-template-id 形式的类的指针,则转换后的A可以是指向推导的A指向的派生类的指针。

因此,类型推导允许某些隐式转换,涉及const/volatile和/或派生到基数的转换,但不考虑指向成员的指针的隐式转换。

case1示例中,类型推断失败,并且函数不匹配。

遗憾的是,无法显式指定模板参数包ARGS应替换为空列表。 正如您已经发现的那样,您可以通过自己显式执行指向成员函数转换的必要指针来使其工作,即使它作为隐式转换是有效的。