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

时间:2013-09-24 19:00:49

标签: c++ inheritance function-pointers variadic compiler-bug

假设您有两个结构,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("%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);

发生了什么事?

1 个答案:

答案 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::*)()的隐式转换。

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

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

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

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

  4. 在这种情况下,我们有函数模板声明

    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...) );
    

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

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

    C ++标准(N3485)14.8.2.1p4:

      

    通常,演绎过程会尝试查找模板参数值,这些参数值会使推导的AA相同(在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应该用空列表替换。正如您已经发现的那样,您可以通过显式执行成员函数转换的必要指针来实现此工作,即使它作为隐式转换有效。