18

I have an issue regarding access declarations under g++ (version 5.1).

class Base
{
public:
    void doStuff() {}
};

class Derived : private Base
{
public:
    // Using older access declaration (without using) shoots a warning
    // and results in the same compilation error
    using Base::doStuff;
};

template<class C, typename Func>
void exec(C *c, Func func)
{
    (c->*func)();
}

int main()
{
    Derived d;
    // Until here, everything compiles fine
    d.doStuff();
    // For some reason, I can't access the function pointer
    exec(&d,&Derived::doStuff);
}

g++ fails to compile the above code with:

test.cpp: In instantiation of ‘void exec(C*, Func) [with C = Derived; Func = void (Base::*)()]’: test.cpp:24:27: required from here
test.cpp:17:4: error: ‘Base’ is an inaccessible base of ‘Derived’ (c->*func)();

Even when the function itself can be called (d.doStuff();) the pointer can't be used even though I declared the function as accessible from the outside. Private inheritance is also important, to some extent, because the Derived class chooses to expose only a certain set of members from base(s) which are interface implementations IRL.

NB : this is a question about the language, not class design.

Barry
  • 286,269
  • 29
  • 621
  • 977
maxbc
  • 949
  • 1
  • 8
  • 18

4 Answers4

19

The problem is that &Derived::doStuff isn't actually a pointer to a member of class Derived. From [expr.unary.op]:

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 member m of some class C with type T, the result has type “pointer to member of class C of type T” and is a prvalue designating C::m.

doStuff is not a member of Derived. It is a member of Base. Hence it has type pointer to member of Base, or void (Base::*)(). What the using-declaration does here is simply an aid to overload resolution, from [namespace.udecl]:

For the purpose of overload resolution, the functions which are introduced by a using-declaration into a derived class will be treated as though they were members of the derived class.

That's why d.doStuff() works. However, through the function pointer, you're trying to call a Base member function on a Derived object. There's no overload resolution here since you're using a function pointer directly, so the base class function would be inaccessible.

You might think you could just cast &Derived::doStuff to the "correct" type:

exec(&d, static_cast<void (Derived::*)()>(&Derived::doStuff));

But you can't do that either according to [conv.mem], since again Base is an inaccessible base of Derived:

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 derived class (Clause 10) of B. If B is an inaccessible (Clause 11), ambiguous (10.2), or virtual (10.1) base class of D, or a base class of a virtual base class of D, a program that necessitates this conversion is ill-formed.

Barry
  • 286,269
  • 29
  • 621
  • 977
3

I guess the reason is that the member function is not really part of the derived class, but rather of the base class. This can be shown somehow empirically by inspecting the type of the member function pointer and comparing it with a pointer to the base member function:

cout << typeid(&Derived::doStuff).name() << endl
  << typeid(& Base::doStuff).name() << endl;

Live here.

I'm currently searching the standard for some background on this. Barry's answer holds the respective parts of the standard.

Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
1

According to the stardard [namespace.udecl]:

A using-declaration introduces a name into the declarative region in which the using-declaration appears.

If a using-declaration names a constructor (3.4.3.1), it implicitly declares a set of constructors in the class in which the using-declaration appears (12.9); otherwise the name specified in a using-declaration is a synonym for a set of declarations in another namespace or class.

So you're just introducing Base::doStuff into the Derived region, it's still a member function of Base.

Then exec is instantiated as exec<Derived, void (Base::*)()>, but it can't cast a Derived* to Base* because of the private inheritance.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
1

From the C++11 Standard, §7.3.3 [namespace.udecl], 18:

class A
{
private:
    void f( char );
public:
    void f( int );
protected:
    void g();
};
class B : public A
{
    using A::f; // error: A::f(char) is inaccessible
public:
    using A::g;
    // B::g is a public synonym for A::g
};

Note the B::g is a public synonym for A::g part. When you take the address of Derived::doStuff, GCC is creating a pointer to member function of type void(Base::*)(), and the standard says it's doing well. So, I think the compile time error is fair.

Paolo M
  • 12,403
  • 6
  • 52
  • 73