2

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

到底是怎么回事?

4

1 回答 1

1

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

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

但它也是合法的

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

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

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

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

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

  3. 将推导出的模板参数代入函数声明。

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

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

其中模板参数是Tand ARGS,函数调用表达式

case1<Generic_B>(&Generic_B::p1)

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

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

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

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

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

C++ 标准 (N3485) 14.8.2.1p4:

一般来说,推导过程试图找到模板参数值,使推导的值AA(在A如上所述转换类型之后)相同。但是,有三种情况允许不同:

  • 如果原始P是引用类型,则推导的A(即引用所引用的类型)可以比转换后的 cv 更合格A

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

  • 如果P是一个类并且P具有simple-template-id的形式,那么转换后的A可以是 deduced 的派生类A。同样,如果P是一个指向simple-template-id形式的类的指针,则转换后的A可以是指向 deduced 指向的派生类的指针A

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

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

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

于 2013-09-24T20:56:28.637 回答