以下代码无法在 Linux 上使用 GCC 7.2.0 和 Clang 5.0.0 进行编译。
#include <iostream>
struct A
{
void f()
{
std::cout << "Hello, world!\n";
}
};
struct B : private A
{
using A::f;
};
int main()
{
B b;
void (B::*f)() = &B::f; // Error: 'A' is an inaccessible base of 'B'
(b.*f)();
}
这符合标准吗? B
中的公共(public) using 声明不应该允许透明地获取指向 B::f
的成员函数指针,而不是涉及 A::f
的可访问性超出了 B
的视角?
最佳答案
是的,你的程序格式不正确。
C++17 (N4659) [namespace.udecl]/16(强调我的):
For the purpose of overload resolution, the functions that are introduced by a using-declaration into a derived class are treated as though they were members of the derived class. In particular, the implicit
this
parameter shall be treated as if it were a pointer to the derived class rather than to the base class. This has no effect on the type of the function, and in all other respects the function remains a member of the base class.
换句话说,using-declaration 不会添加 B
的成员,它只是为同一成员 A::f 添加第二个名称
。第二个名称可以通过名称查找来选择,并用于对该名称的使用进行可访问性检查,但除此之外,除了重载解析中所指出的之外,它等同于原始成员。
[expr.unary.op]/3:
The result of the unary
&
operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id. If the operand is a qualified-id naming a non-static or variant memberm
of some classC
with typeT
, the result has type "pointer to member of classC
of typeT
" and is a prvalue designatingC::m
.
因此,即使您使用的 qualified-id B::f
是用 class B
的名称和限定名称查找拼写的查找 B
中 using-declaration 引入的名称,表达式命名的实际函数是 A
的成员,因此表达式 &B::f
具有类型“指向返回 void
的 () 类型函数的类 A
成员的指针”,或作为 type- id,void (A::*)()
。您可以通过添加到示例中来验证这一点:
#include <type_traits>
static_assert(std::is_same<decltype(&B::f), void (A::*)()>::value, "error");
最后,在 [conv.mem]/2 中:
A prvalue of type "pointer to member of
B
of type cvT
", whereB
is a class type, can be converted to a prvalue of type "pointer to member ofD
of type cvT
", whereD
is a derived class ofB
. IfB
is an inaccessible, ambiguous, or virtual base class ofD
, or a base class of a virtual base class ofD
, a program that necessitates this conversion is ill-formed.
因此,将指向成员函数的指针命名是有效的,但将其从 void (A::*)()
转换为 void (B::*)()
不是,因为无法从 main
访问继承。
作为解决方法,除了 B
中的成员函数本身之外,您还可以提供对成员函数指针的访问:
struct B : private A
{
using A::f;
using func_ptr_type = void (B::*)();
static constexpr func_ptr_type f_ptr = &A::f;
};
或者,如果您确实必须这样做,请使用 C 风格的强制转换。在某些情况下,允许 C 样式强制转换忽略类继承关系的可访问性,其中任何 C++ 样式强制转换都不能以相同的结果进行编译。 (reinterpret_cast
还可以将任何指向成员函数的指针转换为任何其他函数,但其结果未指定。)
int main()
{
B b;
void (B::*f)() = (void (B::*)()) &B::f;
(b.*f)();
}
请注意问题附带的注释:如果将 main
更改为
int main()
{
B b;
auto f = &B::f;
(b.*f)();
}
那么 f
的类型是 void (A::*)()
,如上所述。但随后你遇到了 [expr.mptr.oper]/2(再次强调我的):
The binary operator
.*
binds its second operand, which shall be of type "pointer to member ofT
" to its first operand, which shall be a glvalue of classT
or of a class of whichT
is an unambiguous and accessible base class.
所以,你仍然遇到这样的问题:成员函数与其原始类相关联,除了在 B
范围内之外,不能被视为 B
的成员以及任何 friend 。
关于c++ - 获取指向私有(private)基类别名成员函数的指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46750163/