Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Public access declaration does not affect member function pointers?

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.

like image 501
maxbc Avatar asked Jul 20 '15 13:07

maxbc


People also ask

Can we create a pointer to a member function?

You can use pointers to member functions in the same manner as pointers to functions. You can compare pointers to member functions, assign values to them, and use them to call member functions.

Can we call member function using this pointer from constructor?

the constructor is the first function which get called. and we can access the this pointer via constructor for the first time. if we are able to get the this pointer before constructor call (may be via malloc which will not call constructor at all), we can call member function even before constructor call.

How do you declare a function pointer in C++?

We declare the function pointer, i.e., void (*ptr)(char*). The statement ptr=printname means that we are assigning the address of printname() function to ptr. Now, we can call the printname() function by using the statement ptr(s).

What is pointer to member in C++?

Pointer-to-Member Operators The C++ language has specific operators to represent pointer-to-member access. The . * and ->* operator function returns specific class member values for the object that it represents on the left side of an expression while the right side specifies a class member.


2 Answers

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.

like image 62
Barry Avatar answered Oct 06 '22 00:10

Barry


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.

like image 20
Daniel Jour Avatar answered Oct 06 '22 01:10

Daniel Jour