不能使用指向来自私有基的 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 Uwhere 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

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
}

godbolt example

是有效的 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::fooDerived::foo 是不同的函数,但仍然需要比较等于 Base::foo,因为那是实际实现。
    因此,编译器需要知道所有编译单元中包含 using Base::foo; 的所有 classes,并确保无论何时将它们的 ::fooBase::foo 进行比较,结果都是 true.对于奇怪的边缘情况,这听起来像是大量的实施工作。