views:

249

answers:

4

Hi,

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();
}
+2  A: 

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

Edit: Changed myclass to object

Suroot
I believe you mean "Since `object` is being passed as a parameter, ..."
outis
Look at the modified snippet, please.
AraK
Thanks Outis, I modified above but I was actually referring to the parameter type that was being passed. Sorry for the confusion :)
Suroot
+6  A: 

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
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
WOW... really impressive, I've been scratching my head for days :)
AraK
Hopefully the rationale in my answer provides a bit of an insight as to why it works that way.
Pavel Minaev
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
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
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
A: 

Protected members can only be accessed by friends or derived classes. Some compilers (possibly VC2008 is included in this) have weird behavior where classes and functions declared in the same file are automatically friends, but that is non-standard and you shouldn't rely on it.

Donnie
`access' inherits `myclass`, take a look at the code again please :)
AraK
+12  A: 

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
+1 Thanks man for the great explanation, this thing was a real puzzle for me.
AraK
Todd Gardner
Pavel Minaev
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
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