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

K

K. Frank

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
 
V

Victor Bazarov

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
 
R

Richard Damon

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.
 
K

K. Frank

Hello Victor and Richard!

Thank you for your comments.

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
 
V

Victor Bazarov

Hello Victor and Richard!

Thank you for your comments.



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
 
K

K. Frank

Hi Victor!

Thank you. This makes good sense now.

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.

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

Krice

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.
 
A

Alf P. Steinbach

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
 
K

K. Frank

Hello Alf!

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

On 17.03.2012 16:29, K. Frank wrote:
...

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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,962
Messages
2,570,134
Members
46,690
Latest member
MacGyver

Latest Threads

Top