不能使用指向来自私有基的 public 成员函数的指针
Cannot use pointer to public member function that comes from a private base
考虑这段代码:
class Base {
public:
int foo(int x) const { return 2*x; }
};
class Derived : Base {
public:
using Base::foo;
};
现在Derived
有一个public方法foo可以调用
Derived d;
d.foo(2); // compiles (as it should)
但是,如果我通过指针使用该方法,我将无能为力:
Derived d;
(d.*&Derived::foo)(2); // does not compile because `Derived::foo` expects a pointer to `Base` and `Derived` cannot be casted to its private base class (without a C-style cast).
对于这种行为是否有任何合乎逻辑的解释,或者它可能是标准中的疏忽?
tl;博士:
- 声明成员的class是成员函数指针将绑定到的class。
Derived
上的 ->*
不适用于 Base::
成员函数指针,除非您可以访问 Derived
中的 private Base
(例如在 Derived
的成员函数中或声明为 Derived
的友元函数中。
- c 风格的转换允许您将
Derived*
转换为 Base*
以及这些类型的成员函数指针,即使 Base
不可访问(这对于任何 c++ 风格的转换),例如:
Base* b = (Base*)&d;
在你的例子中是合法的。
1。为什么会得到一个Base::
成员函数指针
9.9 The using
declaration(强调我的)
12 [Note 5: For the purpose of forming a set of candidates during overload resolution, the functions named by a using-declaration in a derived class are treated as though they were direct members of the derived class. In particular, the implicit object parameter is treated as if it were a reference to the derived class rather than to the base class ([over.match.funcs]). This has no effect on the type of the function, and in all other respects the function remains part of the base class. — end note]
因此 using
声明不会为 Derived
创建 foo
的版本,但编译器需要假装它是 Derived::
的成员重载解析的目的。
所以在这种情况下相关的位是 这对函数的类型没有影响 - 即你仍然会得到一个函数指针 Base::
如果你取 foo
.
的地址
注意:唯一的例外是构造函数
2。为什么不能将 ->*
与 Base::
成员指针一起使用
7.6.4 Pointer-to-member operators(强调我的)
3 The binary operator ->*
binds its second operand, which shall be of type “pointer to member of T” to its first operand, which shall be of type “pointer to U” where U is either T or a class of which T is an unambiguous and accessible base class. The expression E1->*E2
is converted into the equivalent form (*(E1)).*E2
.
这里的问题是,在您使用 foo
指针 Base
的点上 不可访问 ,因此调用不起作用。
3。如何让它发挥作用
成员函数实际上需要可转换为任何派生类型,只要满足几个条件:
7.3.13 Pointer-to-member conversions(强调我的)
2 A prvalue of type “pointer to member of B of type cv T”, where B is a class type, can be converted to a prvalue of type “pointer to member of D of type cv T”, where D is a complete class derived ([class.derived]) from B. If B is an inaccessible ([class.access]), ambiguous ([class.member.lookup]), or virtual ([class.mi]) base class of D, or a base class of a virtual base class of D, a program that necessitates this conversion is ill-formed.
[...]
鉴于 Base
不像您的示例中那样模棱两可或虚拟,我们唯一需要关注的问题是可访问性部分。
或者我们呢?
我们可以使用的标准实际上有一个小漏洞:
7.6.3 Explicit type conversion (cast notation)(强调我的)
4 The conversions performed by
- (4.1) a
const_cast
([expr.const.cast]),
- (4.2) a
static_cast
([expr.static.cast]),
- (4.3) a
static_cast
followed by a const_cast
,
- (4.4) a
reinterpret_cast
([expr.reinterpret.cast]), or
- (4.5) a
reinterpret_cast
followed by a const_cast
,
can be performed using the cast notation of explicit type conversion. The same semantic restrictions and behaviors apply, with the exception that in performing a static_cast
in the following situations the conversion is valid even if the base class is inaccessible:
- (4.6) a pointer to an object of derived class type or an lvalue or rvalue of derived class type may be explicitly converted to a pointer or reference to an unambiguous base class type, respectively;
- (4.7) a pointer to member of derived class type may be explicitly converted to a pointer to member of an unambiguous non-virtual base class type;
- (4.8) a pointer to an object of an unambiguous non-virtual base class type, a glvalue of an unambiguous non-virtual base class type, or a pointer to member of an unambiguous non-virtual base class type may be explicitly converted to a pointer, a reference, or a pointer to member of a derived class type, respectively.
[...]
因此,尽管任何 C++ 转换方法(如 static_cast
/ reinterpret_cast
等...)都不允许从 Base::*
转换为 Derived::*
,允许 c 风格的转换执行它,即使 base-class 不可访问.
例如:
int main() {
Derived d;
auto fn = &Derived::foo;
// cast to base (only legal with c-style cast)
// Base* b = static_cast<Base*>(&d); // not legal
// Base* b = reinterpret_cast<Base*>(&d); // not legal
Base* b = (Base*)&d; // legal
(b->*fn)(12);
// cast member function pointer to derived
// (also only legal with c-style cast)
using MemFn = int (Derived::*)(int) const;
// auto fnD = static_cast<MemFn>(fn); // not legal
// auto fnD = reinterpret_cast<MemFn>(fn); // not legal
auto fnD = (MemFn)fn; // legal
(d.*fnD)(12);
// or as a one liner (provided by @KamilCuk in the comments):
// slightly hard to read, but still legal c++:
(d.*((int(decltype(d)::*)(int))&decltype(d)::foo))(12); // legal
}
是有效的 c++。
所以只需将 Derived
转换为 Base
或将成员函数指针转换为绑定到 Derived
的成员函数指针。
标准中甚至有一个例子可以做到这一点:11.8.3 Accessibility of base classes and base class members (3)
4。为什么 &Derived::foo
return 没有 Derived::*
memfn 指针?
因为标准是这么说的。我不知道他们为什么这样决定,但我可以推测可能的原因是:
您可以检查哪个最派生的 class 实现了给定的成员函数。如果 &Derived::foo
将 return 一个 Derived::*
指针,这将中断。 (例如,这可以与 CRTP 一起使用来检查给定的 Derived
class 是否为 Base
的给定成员提供了新定义)
例如:
class Base {
public:
int foo(int x) const { return 2*x; }
};
class Derived : private Base {
public:
using Base::foo;
};
template<class T>
struct implementing_class_helper;
template<class T, class R>
struct implementing_class_helper<R T::*> {
typedef T type;
};
template<class T>
struct implementing_class : implementing_class_helper<typename std::remove_cv<T>::type> {
};
template<class T>
using implementing_class_t = implementing_class<T>;
int main() {
static_assert(std::is_same_v<
typename implementing_class<decltype(&Derived::foo)>::type,
Base
>, "Shenanigans!");
}
如果要创建存根函数,例如:
class Base {
public:
int foo(int x) const { return 2*x; }
};
class Derived : Base {
public:
// pretending using Base::foo; would result in this:
int foo(int x) { return Bar::foo(x); }
};
编译器现在会有问题,因为 Base::foo
和 Derived::foo
是不同的函数,但仍然需要比较等于 Base::foo
,因为那是实际实现。
因此,编译器需要知道所有编译单元中包含 using Base::foo;
的所有 classes,并确保无论何时将它们的 ::foo
与 Base::foo
进行比较,结果都是 true
.对于奇怪的边缘情况,这听起来像是大量的实施工作。
考虑这段代码:
class Base {
public:
int foo(int x) const { return 2*x; }
};
class Derived : Base {
public:
using Base::foo;
};
现在Derived
有一个public方法foo可以调用
Derived d;
d.foo(2); // compiles (as it should)
但是,如果我通过指针使用该方法,我将无能为力:
Derived d;
(d.*&Derived::foo)(2); // does not compile because `Derived::foo` expects a pointer to `Base` and `Derived` cannot be casted to its private base class (without a C-style cast).
对于这种行为是否有任何合乎逻辑的解释,或者它可能是标准中的疏忽?
tl;博士:
- 声明成员的class是成员函数指针将绑定到的class。
->*
不适用于Base::
成员函数指针,除非您可以访问Derived
中的private Base
(例如在Derived
的成员函数中或声明为Derived
的友元函数中。- c 风格的转换允许您将
Derived*
转换为Base*
以及这些类型的成员函数指针,即使Base
不可访问(这对于任何 c++ 风格的转换),例如:
在你的例子中是合法的。Base* b = (Base*)&d;
Derived
上的 1。为什么会得到一个Base::
成员函数指针
9.9 The using
declaration(强调我的)
12 [Note 5: For the purpose of forming a set of candidates during overload resolution, the functions named by a using-declaration in a derived class are treated as though they were direct members of the derived class. In particular, the implicit object parameter is treated as if it were a reference to the derived class rather than to the base class ([over.match.funcs]). This has no effect on the type of the function, and in all other respects the function remains part of the base class. — end note]
因此 using
声明不会为 Derived
创建 foo
的版本,但编译器需要假装它是 Derived::
的成员重载解析的目的。
所以在这种情况下相关的位是 这对函数的类型没有影响 - 即你仍然会得到一个函数指针 Base::
如果你取 foo
.
注意:唯一的例外是构造函数
2。为什么不能将 ->*
与 Base::
成员指针一起使用
7.6.4 Pointer-to-member operators(强调我的)
3 The binary operator
->*
binds its second operand, which shall be of type “pointer to member of T” to its first operand, which shall be of type “pointer to U” where U is either T or a class of which T is an unambiguous and accessible base class. The expressionE1->*E2
is converted into the equivalent form(*(E1)).*E2
.
这里的问题是,在您使用 foo
指针 Base
的点上 不可访问 ,因此调用不起作用。
3。如何让它发挥作用
成员函数实际上需要可转换为任何派生类型,只要满足几个条件:
7.3.13 Pointer-to-member conversions(强调我的)
2 A prvalue of type “pointer to member of B of type cv T”, where B is a class type, can be converted to a prvalue of type “pointer to member of D of type cv T”, where D is a complete class derived ([class.derived]) from B. If B is an inaccessible ([class.access]), ambiguous ([class.member.lookup]), or virtual ([class.mi]) base class of D, or a base class of a virtual base class of D, a program that necessitates this conversion is ill-formed.
[...]
鉴于 Base
不像您的示例中那样模棱两可或虚拟,我们唯一需要关注的问题是可访问性部分。
或者我们呢?
我们可以使用的标准实际上有一个小漏洞:
7.6.3 Explicit type conversion (cast notation)(强调我的)
4 The conversions performed by
- (4.1) a
const_cast
([expr.const.cast]),- (4.2) a
static_cast
([expr.static.cast]),- (4.3) a
static_cast
followed by aconst_cast
,- (4.4) a
reinterpret_cast
([expr.reinterpret.cast]), or- (4.5) a
reinterpret_cast
followed by aconst_cast
,can be performed using the cast notation of explicit type conversion. The same semantic restrictions and behaviors apply, with the exception that in performing a
static_cast
in the following situations the conversion is valid even if the base class is inaccessible:
- (4.6) a pointer to an object of derived class type or an lvalue or rvalue of derived class type may be explicitly converted to a pointer or reference to an unambiguous base class type, respectively;
- (4.7) a pointer to member of derived class type may be explicitly converted to a pointer to member of an unambiguous non-virtual base class type;
- (4.8) a pointer to an object of an unambiguous non-virtual base class type, a glvalue of an unambiguous non-virtual base class type, or a pointer to member of an unambiguous non-virtual base class type may be explicitly converted to a pointer, a reference, or a pointer to member of a derived class type, respectively.
[...]
因此,尽管任何 C++ 转换方法(如 static_cast
/ reinterpret_cast
等...)都不允许从 Base::*
转换为 Derived::*
,允许 c 风格的转换执行它,即使 base-class 不可访问.
例如:
int main() {
Derived d;
auto fn = &Derived::foo;
// cast to base (only legal with c-style cast)
// Base* b = static_cast<Base*>(&d); // not legal
// Base* b = reinterpret_cast<Base*>(&d); // not legal
Base* b = (Base*)&d; // legal
(b->*fn)(12);
// cast member function pointer to derived
// (also only legal with c-style cast)
using MemFn = int (Derived::*)(int) const;
// auto fnD = static_cast<MemFn>(fn); // not legal
// auto fnD = reinterpret_cast<MemFn>(fn); // not legal
auto fnD = (MemFn)fn; // legal
(d.*fnD)(12);
// or as a one liner (provided by @KamilCuk in the comments):
// slightly hard to read, but still legal c++:
(d.*((int(decltype(d)::*)(int))&decltype(d)::foo))(12); // legal
}
是有效的 c++。
所以只需将 Derived
转换为 Base
或将成员函数指针转换为绑定到 Derived
的成员函数指针。
标准中甚至有一个例子可以做到这一点:11.8.3 Accessibility of base classes and base class members (3)
4。为什么 &Derived::foo
return 没有 Derived::*
memfn 指针?
因为标准是这么说的。我不知道他们为什么这样决定,但我可以推测可能的原因是:
您可以检查哪个最派生的 class 实现了给定的成员函数。如果
&Derived::foo
将 return 一个Derived::*
指针,这将中断。 (例如,这可以与 CRTP 一起使用来检查给定的Derived
class 是否为Base
的给定成员提供了新定义) 例如:class Base { public: int foo(int x) const { return 2*x; } }; class Derived : private Base { public: using Base::foo; }; template<class T> struct implementing_class_helper; template<class T, class R> struct implementing_class_helper<R T::*> { typedef T type; }; template<class T> struct implementing_class : implementing_class_helper<typename std::remove_cv<T>::type> { }; template<class T> using implementing_class_t = implementing_class<T>; int main() { static_assert(std::is_same_v< typename implementing_class<decltype(&Derived::foo)>::type, Base >, "Shenanigans!"); }
如果要创建存根函数,例如:
class Base { public: int foo(int x) const { return 2*x; } }; class Derived : Base { public: // pretending using Base::foo; would result in this: int foo(int x) { return Bar::foo(x); } };
编译器现在会有问题,因为
Base::foo
和Derived::foo
是不同的函数,但仍然需要比较等于Base::foo
,因为那是实际实现。
因此,编译器需要知道所有编译单元中包含using Base::foo;
的所有 classes,并确保无论何时将它们的::foo
与Base::foo
进行比较,结果都是true
.对于奇怪的边缘情况,这听起来像是大量的实施工作。