C++ can';通过一个变量函数传递指向父类中方法的函数指针——编译器错误?

C++ can';通过一个变量函数传递指向父类中方法的函数指针——编译器错误?,c++,inheritance,function-pointers,variadic,compiler-bug,C++,Inheritance,Function Pointers,Variadic,Compiler Bug,假设您有两个结构,Generic\u A和Generic\u BGeneric\u B源自Generic\u A。为什么当Generic\u B尝试访问其父级Generic\u 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编译,复制了以

假设您有两个结构,
Generic\u A
Generic\u B
Generic\u B
源自
Generic\u A
。为什么当
Generic\u B
尝试访问其父级
Generic\u 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);
}

发生了什么事

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

首先,表达式
&泛型B::p1
的类型是
void(泛型A::*)()
。编译器使用
Generic\u B::
限定其名称查找,并查找
Generic\u A
的成员。表达式类型取决于找到的成员的定义,而不是限定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...) );
    
    其中,显式模板参数为
    Generic\u B
    ,函数参数为
    Generic\u 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
      (即参考引用的类型)可能比转换后的
      a
      更符合cv条件

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

    • 如果
      p
      是一个类,并且
      p
      具有表单简单模板id,则转换后的
      a
      可以是推导出的
      a
      的派生类。同样,如果
      P
      是指向表单简单模板id的类的指针,则转换后的
      a
      可以是指向导出的
      a
      所指向的派生类的指针

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

    案例1
    示例中,类型推断失败,且函数不匹配


    不幸的是,无法明确指定模板参数pack
    ARGS
    应替换为空列表。正如您已经发现的那样,您可以通过自己显式地执行必要的成员函数指针转换来实现这一点,即使它在其他方面作为隐式转换是有效的。

    如果您能够格式化代码以使其更易于阅读,这将有所帮助。例如,很明显,在泛型_A::p1()的定义之后有一个额外的分号。(这不是问题,只是一个观察)在cast示例中缺少一个
    。我想你的意思是把它放在星号后面。我不认为这是一个编译器错误<代码>&Generic_B::p1的类型为
    void(Generic_A::*)()
    ,因此需要转换为
    void(Generic_B:*)()
    (根据[conv.mem]/2进行隐式转换)。但是由于这种转换,无法对参数进行类型推断。如果省略显式模板参数,则可以进行类型推断(并且可以正常工作):
    case2(&Generic_B::p1)
    @DyP:但是您可能想调用
    case1
    ,而不是
    case1
    case1<Generic_B>(&Generic_B::p1)
    
    void case1( void (Generic_B::*p)(ARGS...) );