Can't take pointer-to-member of protected member of base class

Discussion in 'C++' started by K. Frank, Mar 17, 2012.

  1. K. Frank

    K. Frank Guest

    Hello Group!

    There is a detail of the protected-member access rules that I don't
    understand.
    I would expect to be able to take a pointer-to-member of a protected
    member
    of a base class in a derived class, but I can't.

    Here is a sample compilation unit that illustrates the issue:

    g++ (4.7.0) gives the following error:

    c:/> g++ -c pm_test.cpp
    pm_test.cpp: In member function 'virtual void
    pm_derived::assign_pm()':
    pm_test.cpp:6: error: 'int pm_base::m' is protected
    pm_test.cpp:13: error: within this context

    Here is the test code:

    ===== pm_test.cpp =====

    class pm_base {
    public:
    virtual void assign_pm() { pm = &pm_base::m; }

    protected: // compiles if m is public
    int m; // this is line 6
    protected:
    int pm_base::*pm;
    };

    class pm_derived : protected pm_base {
    public:
    virtual void assign_pm() { pm = &pm_base::m; } // this is line 13
    };

    ==========

    (I get a similar error from the Comeau online compiler.)

    I would have expected that a derived class would have full access to
    the
    protected members of the base class, just as it has full access to its
    own
    protected members. But this interpretation seems not to apply to
    taking
    a pointer-to-member of a protected member of the base class.

    Could someone explain what is going on here? Also, what might be the
    rationale for this behavior.

    Thanks for any insight.


    K. Frank
     
    K. Frank, Mar 17, 2012
    #1
    1. Advertising

  2. On 3/17/2012 11:29 AM, K. Frank wrote:
    > Hello Group!
    >
    > There is a detail of the protected-member access rules that I don't
    > understand.
    > I would expect to be able to take a pointer-to-member of a protected
    > member
    > of a base class in a derived class, but I can't.
    >
    > Here is a sample compilation unit that illustrates the issue:
    >
    > g++ (4.7.0) gives the following error:
    >
    > c:/> g++ -c pm_test.cpp
    > pm_test.cpp: In member function 'virtual void
    > pm_derived::assign_pm()':
    > pm_test.cpp:6: error: 'int pm_base::m' is protected
    > pm_test.cpp:13: error: within this context
    >
    > Here is the test code:
    >
    > ===== pm_test.cpp =====
    >
    > class pm_base {
    > public:
    > virtual void assign_pm() { pm =&pm_base::m; }
    >
    > protected: // compiles if m is public
    > int m; // this is line 6
    > protected:
    > int pm_base::*pm;
    > };
    >
    > class pm_derived : protected pm_base {
    > public:
    > virtual void assign_pm() { pm =&pm_base::m; } // this is line 13
    > };
    >
    > ==========
    >
    > (I get a similar error from the Comeau online compiler.)
    >
    > I would have expected that a derived class would have full access to
    > the
    > protected members of the base class, just as it has full access to its
    > own
    > protected members. But this interpretation seems not to apply to
    > taking
    > a pointer-to-member of a protected member of the base class.
    >
    > Could someone explain what is going on here? Also, what might be the
    > rationale for this behavior.
    >
    > Thanks for any insight.


    Access to protected members of the derived class is only allowed though
    the 'this' pointer. A pointer to a member is not "specific" enough (not
    restricted to 'this') and therefore is prohibited. It's done to prevent
    accessing to base class members of the object that is not of the same
    class as '*this'.

    V
    --
    I do not respond to top-posted replies, please don't ask
     
    Victor Bazarov, Mar 17, 2012
    #2
    1. Advertising

  3. On 3/17/12 5:11 PM, Victor Bazarov wrote:

    > Access to protected members of the derived class is only allowed though
    > the 'this' pointer. A pointer to a member is not "specific" enough (not
    > restricted to 'this') and therefore is prohibited. It's done to prevent
    > accessing to base class members of the object that is not of the same
    > class as '*this'.
    >
    > V


    I believe it is actually any pointer of the derived type (but not
    pointers of the base type). Thus a member function can get to the
    protected internals of any object of the derived type that it has access to.
     
    Richard Damon, Mar 17, 2012
    #3
  4. K. Frank

    K. Frank Guest

    Hello Victor and Richard!

    Thank you for your comments.

    On Mar 17, 7:43 pm, Richard Damon <>
    wrote:
    > On 3/17/12 5:11 PM, Victor Bazarov wrote:
    >
    > > Access to protected members of the derived class is only allowed though
    > > the 'this' pointer.  A pointer to a member is not "specific" enough (not
    > > restricted to 'this') and therefore is prohibited.  It's done to prevent
    > > accessing to base class members of the object that is not of the same
    > > class as '*this'.

    >
    > > V

    >
    > I believe it is actually any pointer of the derived type (but not
    > pointers of the base type). Thus a member function can get to the
    > protected internals of any object of the derived type that it has access to.


    I've mulled this over a little bit, and it sort of makes sense,
    but I still don't think I'm looking at it the right way.

    Here's an additional aspect that is confusing me: I *am* able to
    take a pointer-to-member of a protected member of the base class
    if I do it using a pointer-to-member of the derived class. What
    I mean by this is shown in the modified sample code:

    ==========

    class pm_base {
    public:
    virtual void assign_pm() { pm = &pm_base::m; }
    protected:
    int m;
    protected:
    int pm_base::*pm;
    };

    class pm_derived : protected pm_base {
    public:
    // virtual void assign_pm() { pm = &pm_base::m; } // this line
    doesn't compile
    virtual void assign_pmd() { pmd = &pm_derived::m; } // this line
    does
    protected:
    int pm_derived::*pmd;
    };

    ==========

    This now compiles fine. To emphasize, although in the derived class
    I cannot say "pm = &pm_base::m;", I can say "pmd = &pm_derived::m;",
    even though in both cases it's the same m -- a protected data member
    of the base class -- that I would be getting access to through the
    pointer-to-member.

    I don't see how to analyze this to see precisely what isn't allowed,
    and why not.

    Any additional thoughts would be appreciated.

    Thanks.


    K. Frank
     
    K. Frank, Mar 18, 2012
    #4
  5. On 3/17/2012 10:07 PM, K. Frank wrote:
    > Hello Victor and Richard!
    >
    > Thank you for your comments.
    >
    > On Mar 17, 7:43 pm, Richard Damon<>
    > wrote:
    >> On 3/17/12 5:11 PM, Victor Bazarov wrote:
    >>
    >>> Access to protected members of the derived class is only allowed though
    >>> the 'this' pointer. A pointer to a member is not "specific" enough (not
    >>> restricted to 'this') and therefore is prohibited. It's done to prevent
    >>> accessing to base class members of the object that is not of the same
    >>> class as '*this'.

    >>
    >>> V

    >>
    >> I believe it is actually any pointer of the derived type (but not
    >> pointers of the base type). Thus a member function can get to the
    >> protected internals of any object of the derived type that it has access to.

    >
    > I've mulled this over a little bit, and it sort of makes sense,
    > but I still don't think I'm looking at it the right way.
    >
    > Here's an additional aspect that is confusing me: I *am* able to
    > take a pointer-to-member of a protected member of the base class
    > if I do it using a pointer-to-member of the derived class. What
    > I mean by this is shown in the modified sample code:
    >
    > ==========
    >
    > class pm_base {
    > public:
    > virtual void assign_pm() { pm =&pm_base::m; }
    > protected:
    > int m;
    > protected:
    > int pm_base::*pm;
    > };
    >
    > class pm_derived : protected pm_base {
    > public:
    > // virtual void assign_pm() { pm =&pm_base::m; } // this line
    > doesn't compile
    > virtual void assign_pmd() { pmd =&pm_derived::m; } // this line
    > does
    > protected:
    > int pm_derived::*pmd;
    > };
    >
    > ==========
    >
    > This now compiles fine. To emphasize, although in the derived class
    > I cannot say "pm =&pm_base::m;", I can say "pmd =&pm_derived::m;",
    > even though in both cases it's the same m -- a protected data member
    > of the base class -- that I would be getting access to through the
    > pointer-to-member.
    >
    > I don't see how to analyze this to see precisely what isn't allowed,
    > and why not.


    If you have a pointer-to-member-of-base, you can [try to] access it
    using any pointer (or reference) to any derived class of that base, not
    just *the* derived class in which you store that pointer. That means
    you will be accessing a member of a different branch of the hierarchy,
    which isn't allowed. Consider:

    class base {
    protected:
    int a;
    };

    class derived_one : protected base {
    };

    class derived_two : protected base {
    int base::*member;
    derived_two() : member(&base::a) {} // suppose we allow it
    void foo(derived_one* other) {
    (other->*member) = 42; // here you're changing some other
    // object, not yours, not your base
    }
    };

    I think that was the rationale. I am not sure though, perhaps somebody
    else can confirm (or refute).

    V
    --
    I do not respond to top-posted replies, please don't ask
     
    Victor Bazarov, Mar 18, 2012
    #5
  6. K. Frank

    K. Frank Guest

    Hi Victor!

    Thank you. This makes good sense now.

    On Mar 17, 10:22 pm, Victor Bazarov <> wrote:
    > On 3/17/2012 10:07 PM, K. Frank wrote:
    > ...
    > > This now compiles fine.  To emphasize, although in the derived class
    > > I cannot say "pm =&pm_base::m;", I can say "pmd =&pm_derived::m;",
    > > even though in both cases it's the same m -- a protected data member
    > > of the base class -- that I would be getting access to through the
    > > pointer-to-member.

    >
    > > I don't see how to analyze this to see precisely what isn't allowed,
    > > and why not.

    >
    > If you have a pointer-to-member-of-base, you can [try to] access it
    > using any pointer (or reference) to any derived class of that base, not
    > just *the* derived class in which you store that pointer.  That means
    > you will be accessing a member of a different branch of the hierarchy,
    > which isn't allowed.


    Ah, okay. This all hangs together now.

    > Consider:
    >
    >      class base {
    >      protected:
    >         int a;
    >      };
    >
    >      class derived_one : protected base {
    >      };
    >
    >      class derived_two : protected base {
    >         int base::*member;
    >         derived_two() : member(&base::a) {} // suppose we allow it
    >         void foo(derived_one* other) {
    >            (other->*member) = 42; // here you're changing some other
    >                                   // object, not yours, not your base
    >         }
    >      };
    >
    > I think that was the rationale.  I am not sure though, perhaps somebody
    > else can confirm (or refute).


    Yes, this would seem to be the likely explanation.

    >
    > V


    Thank you again.

    (And now, just to confuse myself further, I'm looking at how
    pointer-to-member-functions work when the member functions are
    virtual and are overridden.)


    K. Frank
     
    K. Frank, Mar 19, 2012
    #6
  7. K. Frank

    Krice Guest

    On 18 maalis, 04:07, "K. Frank" <> wrote:
    > This now compiles fine.


    Many things can compile fine in C++ but still fail.
    Pointers have a different task in C++ than they have in C,
    and when you try to think why something pointer related
    wont work in C++ you are already sidetracked.
     
    Krice, Mar 19, 2012
    #7
  8. On 17.03.2012 16:29, K. Frank wrote:
    >
    > g++ (4.7.0) gives the following error:
    >
    > c:/> g++ -c pm_test.cpp
    > pm_test.cpp: In member function 'virtual void
    > pm_derived::assign_pm()':
    > pm_test.cpp:6: error: 'int pm_base::m' is protected
    > pm_test.cpp:13: error: within this context
    >
    > Here is the test code:
    >
    > ===== pm_test.cpp =====
    >
    > class pm_base {
    > public:
    > virtual void assign_pm() { pm =&pm_base::m; }
    >
    > protected: // compiles if m is public
    > int m; // this is line 6
    > protected:
    > int pm_base::*pm;
    > };
    >
    > class pm_derived : protected pm_base {
    > public:
    > virtual void assign_pm() { pm =&pm_base::m; } // this is line 13
    > };
    >
    > ==========
    >
    > (I get a similar error from the Comeau online compiler.)
    >
    > I would have expected that a derived class would have full access to
    > the protected members of the base class, just as it has full access to its
    > own protected members. But this interpretation seems not to apply to
    > taking a pointer-to-member of a protected member of the base class.


    An instance of a derived class does have full access to the public and
    protected members of any accessible base class part of itself.

    The ideal, to prevent "hacks" of classes by deriving from some common
    base, would be that an instance of a derived class could not go beyond
    the access outlined above, e.g. that it could not access protected parts
    of a Base& (even though it can access such parts of itself or other
    instances of its own class or some derived class).

    For example,


    <code>
    class Base
    {
    protected:
    int b_;
    };

    class Derived
    : public Base
    {
    public:
    static int& bOfDerived( Derived& derivedObject )
    {
    return derivedObject.b_; // OK.
    }

    static int& bOfBase( Base& baseObject )
    {
    return baseObject.b_; // !Nyet
    }
    };

    int main() {}
    </code>


    Self test: if the rule above didn't exist, how could you easily
    /inadvertently/ access parts of someone else's class, when it shares
    some common base with your class?

    This rule does not prevent intentional hacking. It is only about
    inadvertent (and then usually disastrous) access. As example, let's
    intentionally hack into the protected member "c" of a "std::stack", by
    just a little friendly static_cast'ing.

    Heads-up: as you will see below (if you read on), member pointers
    provide a loophole -- they're very low-level, sort of like "goto" --
    where it is possible to do this without any casting, and indeed
    without any conversion! :)


    <code>
    #include <iostream> // std::wcout, std::endl
    #include <stack> // std::stack
    #include <utility> // std::begin, std::end
    using namespace std;

    typedef stack<int> Stack;
    typedef Stack::container_type Container;

    Container const& containerOf( Stack const& st )
    {
    struct Hack: Stack
    {
    static Container const& container( Stack const& st )
    {
    return static_cast<Hack const&>( st ).c;
    }
    };

    return Hack::container( st );
    }

    void display( Stack const& st )
    {
    Container const& c = containerOf( st );
    for( auto it = begin( c ); it != end( c ); ++it )
    {
    wcout << *it << ' ';
    }
    wcout << endl;
    }

    Stack& pushedOn( Stack& st, int const v )
    {
    st.push( v );
    return st;
    }

    Stack& operator<<( Stack&& st, int const v )
    { return pushedOn( st, v ); }

    Stack& operator<<( Stack& st, int const v )
    { return pushedOn( st, v ); }

    int main()
    {
    Stack const st = move( Stack() << 3 << 1 << 4 << 1 << 5 );

    display( st );
    display( st );
    display( st );
    }
    </code>


    The usual question prompting an explanation such as above, is how Base
    (or rather, the questioner) can provide a protected Base setter method
    to Derived classes, so that they can apply that setter on Base& objects?

    Well, there is a difference between accessing a protected something in
    an /instance/ of Base, versus accessing a protected static something
    inherited directly from Base.

    In the latter case, with a sound design there is no chance of
    inadvertently changing something in an instance of someone else's
    derived class, and also it does not open up for hacking:


    <code>
    #include <iostream>
    using namespace std;

    class Base
    {
    protected:
    int b_;

    void setB( int const v ) { b_ = v; }
    static void setB( Base& o, int const v ) { o.setB( v ); }
    };

    class Derived
    : public Base
    {
    public:
    static void foo( Base& o )
    {
    setB( o, 42 ); // Static member, OK.
    o.setB( 666 ); // Instance member, !Nyet.
    }
    };

    int main() {}
    </code>


    The situation with ordinary pointers is the same.

    However, member pointers are not ordinary pointers. They're not
    addresses. They're more like what used to be called /based pointers/,
    i.e. offsets -- the simplest member pointer represents an offset from
    the start of an instance of the class.

    It can be a bit more complicated that, for pointers to virtual methods,
    and when virtual inheritance is employed, but that's the basic idea, the
    basic conceptual picture.

    Now, an offset to some place in a Base instance can clearly be applied
    to a Derived instance and mean the same there - at least in the simplest
    case, and the more complicated cases are just complicated because the
    internals have to support this simple programmers' picture.

    However, an offset to some place in a Derived instance can not in
    general be applied to a Base instance, because generally a Base instance
    need not have anything there. For it can be the offset of some member
    that is introduced down in Derived. So, summing up so far, a "T
    (Base::*)" or "T (Base::*)(args)" can be applied to a Derived object,
    but a "T (Derived::*)" or "T (Derived::*)(args)" can not in general be
    applied to Base object.

    And these are exactly the restrictions that the C++ rules place on
    member pointers, so that the earlier hack can be rewritten as ...


    <code>
    Container const& containerOf( Stack const& st )
    {
    struct Hack: Stack
    {
    static Container const& container( Stack const& st )
    {
    return st.*&Hack::c;
    }
    };

    return Hack::container( st );
    }
    </code>


    which does not involve any casting or conversions, he he.

    The direct reason that this works is that

    `&Hack::c` is of -- ta da! -- type `Container (Stack::*)`

    because c is inherited from Stack.

    Yes, believe it or not, the type of the result of the address operator
    here depends on whether the argument is inherited:


    <code>
    #include <iostream>
    #include <typeinfo>
    using namespace std;

    struct Base { int b; };
    struct Derived: Base { double d; };

    void show( char const expression[], char const value[] )
    {
    wcout << expression << " -> " << value << endl;
    }

    #define SHOW( e ) show( #e, typeid( e ).name() )

    int main()
    {
    SHOW( &Base::b );
    SHOW( &Derived::b );
    SHOW( &Derived::d );
    }
    </code>

    <output>
    &Base::b -> int Base::*
    &Derived::b -> int Base::*
    &Derived::d -> double Derived::*
    </output>


    Now, as you can see this stuff is a little complicated and
    counter-intuitive, well, maybe more than just a little complicated, and
    as a logical consequence of the general behavior member pointers allow
    you to /inadvertently/ circumvent the normal access rules, as shown in
    the Stack hack.

    But this means that I don't know the answer to your question.

    For the member pointer your derived class is not allowed to obtain
    directly, where it cannot do &Base::x, it can obtain nearly as directly
    via &Derived::x. And very painlessly. So I think it's just a "language
    wart", an undesirable but not very serious consequence of the rules, not
    worth special casing to remove.


    Cheers & hth.,

    - Alf
     
    Alf P. Steinbach, Mar 20, 2012
    #8
  9. K. Frank

    K. Frank Guest

    Hello Alf!

    Thank you. This is food for thought and very helpful.

    On Mar 20, 7:08 pm, "Alf P. Steinbach" <alf.p.steinbach
    > wrote:
    > On 17.03.2012 16:29, K. Frank wrote:
    > ...
    > > pm_test.cpp:6: error: 'int pm_base::m' is protected
    > > pm_test.cpp:13: error: within this context

    > ...
    > >    protected:  // compiles if m is public
    > >      int m;  // this is line 6
    > > ...
    > >      virtual void assign_pm() { pm =&pm_base::m; }  // this is line 13
    > > ...

    >
    > An instance of a derived class does have full access to the public and
    > protected members of any accessible base class part of itself.
    >
    > The ideal, to prevent "hacks"...
    > ...
    > This rule does not prevent intentional hacking. It is only about
    > inadvertent (and then usually disastrous) access. As example, let's
    > intentionally hack into the protected member "c" of a "std::stack", by
    > just a little friendly static_cast'ing.
    > ...
    > <code>
    >      #include <iostream>         // std::wcout, std::endl
    >      #include <stack>            // std::stack
    >      #include <utility>          // std::begin, std::end
    >      using namespace std;
    >
    >      typedef stack<int>              Stack;
    >      typedef Stack::container_type   Container;
    >
    >      Container const& containerOf( Stack const& st )
    >      {
    >          struct Hack: Stack
    >          {
    >              static Container const& container( Stack const& st )
    >              {
    >                  return static_cast<Hack const&>( st ).c;
    >              }
    >          };


    I expect that this hack would work on most (all?) implementations.
    But isn't it the case -- please correct me if I'm wrong -- that if
    st, the argument to the static cast, refers only to a Stack, but
    not, polymorphically, to an actual Hack, then the static cast to
    Hack& yields technically undefined behavior?

    >
    >          return Hack::container( st );
    >      }
    > ...
    > ...
    > For the member pointer your derived class is not allowed to obtain
    > directly, where it cannot do &Base::x, it can obtain nearly as directly
    > via &Derived::x.


    Yes, this makes sense. As I noted earlier in the thread,

    :: I *am* able to
    :: take a pointer-to-member of a protected member of the base
    class
    :: if I do it using a pointer-to-member of the derived class.

    :: // this line doesn't compile
    :: // virtual void assign_pm() { pm = &pm_base::m; }
    :: // this line does
    :: virtual void assign_pmd() { pmd = &pm_derived::m; }


    > And very painlessly. So I think it's just a "language
    > wart", an undesirable but not very serious consequence of the rules, not
    > worth special casing to remove.


    I am leaning towards the belief that this is a legitimate and
    natural consequence (even if somewhat complicated and counter-
    intuitive) of how the various rules interact, rather than an
    oversight or flaw in the design. But I'm really not sure.
    Perhaps there could have been a cleaner design in this corner
    of the language.

    > Cheers & hth.,
    >
    > - Alf


    Thanks again for your explanations and detailed examples.


    K. Frank
     
    K. Frank, Mar 22, 2012
    #9
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.

Share This Page