4

The following simple piece of code, compiles with VC2008 but g++ rejects the code:

#include <iostream>

class myclass
{
protected:
    void print() { std::cout << "myclass::print();"; }
};

struct access : private myclass
{
    static void access_print(myclass& object)
    {
        // g++ and Comeau reject this line but not VC++
        void (myclass::*function) () = &myclass::print;

        (object.*function)();
    }
};

int main()
{
    myclass object;
    access::access_print(object);
}

(/W4) is turned on in VC, but it doesn't give any warning.

g++ 4.4.1 gives me an error:

correct.cpp: In static member function ‘static void access::access_print(myclass&)’:
correct.cpp:6: error: ‘void myclass::print()’ is protected

If g++ is correct, how do I access a protected member of a class? is there another way?


@Suroot Do you mean that I shouldn't pass an object of type myclass? It doesn't matter actually, g++ gives the same error but VC compiles the code without any warning.

#include <iostream>

class myclass
{
protected:
    void print() { std::cout << "myclass::print();"; }
};

struct access : private myclass
{
    static void access_print()
    {
        myclass object;
        void (myclass::*function) () = &myclass::print;

        (object.*function)();
    }
};

int main()
{
    access::access_print();
}
Khaled Alshaya
  • 94,250
  • 39
  • 176
  • 234

3 Answers3

13

This is correct. The relevant part of the Standard has been quoted above, but here's the rationale for what it's worth.

The semantics of protected in C++ means "this member can be accessed from methods of this class or any derived classes, but only when dynamic type of object being accessed is guaranteed to be the same as, or derived from, the type of *this".

The latter bit may not quite be obvious, so here is an example:

 class Base {
 protected:
     int X;
 };

class Derived : public Base {
   void Foo(Base* b, Derived* d) {
       this->X = 123; // okay - `this` is definitely either Derived
                      // or inherited from Derived

       d->X = 456;    // also okay, for the same reason

       b->X = 789;    // NOT OKAY; b could point to instance of some other class
                      // Derived2, which isn't inherited from Derived!
   }
};

This is by design - the idea is that Derived2 could have its own opinion on how its protected members should be handled (what are the invariants, etc), and it should be strictly between it and its base class (and its derived classes, but only if it decides to not hide that field by making it private). Cross-hierarchy access is contrary to this model, since it would effectively mean that the base class decides for the entire hierarchy in advance; for very abstract classes on top of deep hierarchies, this can be undesirable.

Now back to your specific problem - it should be fairly obvious by now. Once you obtain a member function pointer, you may call the function pointed to by that pointer with any receiver of a matching type. For a protected method of a base class, this means that, if you could obtain a pointer to it typed to the base class (rather than your own class), you could then call it, passing it a pointer to a type different from your class (or derived from it), violating the rules for protected access outlined above. Therefore, you are not permitted to do this.

Pavel Minaev
  • 99,783
  • 25
  • 219
  • 289
  • +1 Thanks man for the great explanation, this thing was a real puzzle for me. – Khaled Alshaya Nov 24 '09 at 04:17
  • 1
    See, that's what I was thinking when I first read it, but since you can cast the result of &access::print to a void (myclass::*)() doesn't that mean you can apply it willy-nilly to an object with static type myclass irrespective of the dynamic type? – Todd Gardner Nov 24 '09 at 04:28
  • 1
    Wow, go figure. You're absolutely correct, Todd - even though it forces you to write `&Derived::Foo` to get it working (and then it works!), the actual type of the expression is still a pointer to member of `Base`, and you can then successfully use it to access a protected member through a base pointer. Careful reading of the spec is consistent with this interpretation, and so are MSVC 2008 & 2010, g++ 3.4, and Comeau. I've tried Googling around and haven't found any discussion of this particular corner case - I think I'll start a thread on comp.std.c++... – Pavel Minaev Nov 24 '09 at 08:15
  • That said, I think the point of that restriction _may_ have been as I described in this answer, but they just didn't go far enough (forgot to force the type of pointer to be `Derived`?). The intent of `protected` itself is made perfectly clear in the Standard: "Except when forming a pointer to member (5.3.1), the access must be through a pointer to, reference to, or object of the derived class itself (or any class derived from that class) (5.2.5)" - they didn't apply this restriction exactly as worded to member pointers because there's no pointer through which access goes for them... – Pavel Minaev Nov 24 '09 at 08:17
  • Okay, this is a known issue, and was raised before by litb; see http://stackoverflow.com/questions/75538/hidden-features-of-c/1065606#1065606 and http://groups.google.com/group/comp.std.c++/browse_thread/thread/e4d67b76e73bdac1 and http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/ec22da1497adbf96. While there was some discussion on that back then, it didn't go anywere, so I've raised that question again - hopefully it'll garner some reaction (and maybe become a DR): http://groups.google.com/group/comp.std.c++/browse_thread/thread/37046682d4d570ed# – Pavel Minaev Nov 24 '09 at 21:02
6

I believe g++ and comeau are correct. The specifier for a protected member must be of type "access" or derived, so I believe the code:

void (myclass::*function) () = &access::print;

would compile.

I believe this is because of 11.5.1:

... If the access [ed. to a protected member ] is to form a pointer to member, the nested-name-specifier shall name the derived class (or any class derived from that class).

Todd Gardner
  • 13,313
  • 39
  • 51
  • Just tried it in comeau; it works. I never really understood why this rule existed, but I got bit by it awhile ago, and only found it after fair amount of hair pulling. – Todd Gardner Nov 24 '09 at 04:03
  • WOW... really impressive, I've been scratching my head for days :) – Khaled Alshaya Nov 24 '09 at 04:07
  • Hopefully the rationale in my answer provides a bit of an insight as to why it works that way. – Pavel Minaev Nov 24 '09 at 04:11
  • The quoted rule obviously applies but it's not obvious how to find it. It would be nice if gcc would say what rule it's applying. As for what the standards committee might be thinking, for a minute I thought it might be the vtbl, but that doesn't seem likely because pointers to base classes handle vtbls properly. Weird. – Windows programmer Nov 24 '09 at 04:12
  • I find this implementation interesting seeing as you're getting the pointer to a function that only exists within a struct. And seeing as the struct hasn't been declared in this instance I'd be interested to know what the function (myclass::*function)() is actually referencing when called. – Suroot Nov 24 '09 at 04:17
  • The function exists in both the struct and its base type. myclass::*function references the base type's print method because that's what the assignment statement assigned. If the standard permitted this code then it's obvious what would happen. – Windows programmer Nov 24 '09 at 04:31
2

Since object is being passed as a parameter, you cannot access private/protected functions directly.

Edit: Changed myclass to object

Suroot
  • 4,315
  • 1
  • 22
  • 28