I tried following code with VC++2005, VC++2008 and Comeau C/C++:
<code>
class X
{
protected:
void foo() {}
int a;
};
class Y : public X
{
public:
void bar()
{
X::foo(); // OK
int b = X::a; // OK
void (X::*pf)() = &X::foo; // error C2248!
int X::*pa = &X::a; // error C2248!
}
};
</code>
All compilers emit C2248 error or equivalent. I want to understand
why this happens. Why accessing protected members is OK while
taking their pointers is an error?
Thanks
Alex
"VC8 SP1: C++/CLI: taking address of protected bound-member"
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=260265
However the fact that Comeau C/C++ complines, too make me doubt
that.
Alex
struct Base
{
protected:
int myCriticalThing;
public:
// Stuff.
};
struct SomeonesDerived: Base
{
// Stuff
};
struct AlexHack: Base
{
static void hackIt( Base& o )
{
o.myCriticalThing = 666; // Happily, not allowed in C++.
}
};
int main()
{
SomeonesDerived o;
AlexHack::hackIt( o ); // Could, if not for restriction.
}
Cheers, & hth.,
- Alf (just visiting due to George's articles over in clc++ :-))
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
The short answer is that the C++ standard says so:
11.5/1 When a friend or a member function of a derived class references
a protected nonstatic member of a base class, an access check applies in
addition to those described earlier in clause 11. 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). If the access is to form a pointer to
member, the nested-name-specifier shall name the derived class (or any
class derived from that class).
So &X::foo fails the access check, but &Y::foo works.
Here's the long answer. Class Y is presumed to know how it uses
protected members of its base class X, and can access them freely. But
it doesn't necessarily know how some other class Z derived from X might
use X's protected members, and is not allowed to mess up with those.
Consider:
class Z : public X {};
void Y::baz(Z* pz) {
X::foo(); // fine - calls foo() on itself
pz->X::foo(); // doesn't compile - calls foo on Z
int b = X::a; // fine - access its own members
b = pz->X::a; // doesn't compile
}
If &X::foo worked inside Y, baz() could have worked around the
restrictions like this:
void Y::baz(Z* pz) {
void (X::*pf)() = &X::foo; // hypothetical, doesn't compile
(pz->*pf)(); // same as pz->foo()
}
--
With best wishes,
Igor Tandetnik
With sufficient thrust, pigs fly just fine. However, this is not
necessarily a good idea. It is hard to be sure where they are going to
land, and it could be dangerous sitting under them as they fly
overhead. -- RFC 1925
Because this doesn't work either:
class Y : public X
{
public:
void bar()
{
X x;
x.foo(); // fails, Y::bar can access protected X::foo only
through a Y instance
}
};
This should work (I didn't test it):
void (Y::*pf)() = &X::foo;
int Y::*pa = &X::a; // error C2248!
>
> Thanks
> Alex
>
I think you mean:
void (X::*pf)() = &Y::foo;
Alex
Thanks for the elaborate answer. Now it makes sense. I don't
remember ever encountering this dusty corner of the language.
What I tried to do is to find acceptable workaround for the
problem from "How are virtual function calls implemented?" thread
started by Anthony Wieser. Basically, the code looks like this:
class MfcX
{
protected:
virtual void foo() {}
};
class UserY : public MfcX
{
public:
virtual void foo() {}
void bar()
{
// will crash due to bug in VC+2005 SP1
//MfcX::foo();
// nice attempt
void (MfcX::*pf)() = &MfcX::foo;
MfcX* pParent = this;
(pParent->*pf)();
}
};
Now, the above attempt won't compile. Making it using `UserY' name
won't work either:
// another nice attempt
void (UserY::*pf)() = &UserY::foo;
MfcX* pParent = this;
(pParent->*pf)(); // won't compile either
That's about it.
Alex
Visit more often. :) Thanks for the answer.
Alex
If I could do _that_, I would be able to call a method on an object that
doesn't have this method in the first place:
class Base {};
class Derived : public Base {
public:
void foo();
};
void (Base::*pf)() = &Derived::foo;
Base b;
(b.*pf)(); // b.foo() ????
I guess both of you meant
void (Y::*pf)() = &Y::foo;
>Alex Blekhman <tkfx....@yahoo.com> wrote:
>> "Ben Voigt [C++ MVP]" wrote:
>>> This should work (I didn't test it):
>>>
>>> void (Y::*pf)() = &X::foo;
>>> int Y::*pa = &X::a; // error C2248!
>>
>> I think you mean:
>>
>> void (X::*pf)() = &Y::foo;
>
>If I could do _that_, I would be able to call a method on an object that
>doesn't have this method in the first place:
Yes, the implicit cast between derived->base sort of reverses into
base->derived for pointers to members. At least that's how I remember it.
--
Doug Harrison
Visual C++ MVP
No, I meant what I wrote
void (Y::*pf)() = &X::foo;
which is a fully-bound (non-virtual) member function, calling the exact
function X::foo on any instance of Y.
I don't know if the C++ compiler will accept that syntax, it should because
it is type safe. if Y is a subtype of X, then Y -> unit is a supertype of
X -> unit.
Y can access the base implementation of protected member foo, so it should
be able to create a pointer-to-member that does so.
Obviously the "void (Y::*pf)() = &Y::foo;" statement will work.
However, I meant
void (X::*pf)() = &Y::foo;
statement. If `X' actualy contains `foo' member, then everything
compiles happily. If `foo' exists only in `Y', then you'll get
compilation error:
C2440: cannot convert from 'void (__thiscall Y::* )(void)' to
'void (__thiscall X::* )(void)'
Alex
That still shouldn't compile, due to access check on X::foo, as
described in C++ standard 11.5/1. See my earlier answer in this thread.
> I don't know if the C++ compiler will accept that syntax, it should
> because it is type safe.
The problem has nothing to do with type safety, but with access control.
> Y can access the base implementation of protected member foo, so it
> should be able to create a pointer-to-member that does so.
If it were able to do so, it would be able to call foo on some other
class Z derived from X. But it shouldn't be able to call a protected
method on an unrelated class, even if the two share the same base.
>The short answer is that the C++ standard says so:
I was thinking the same thing, but it doesn't seem like the compiler would
let you say the following even if the hypothetical initialization worked:
(pz->*pf)();
for the same reason it doesn't allow:
pz->foo();
However, it would let you say:
(this->*pf)();
which is potentially useful in a limited way. Am I missing something?
Actually, I started this thread because the C++ compiler doesn't
accept that syntax. Igor cited relevant part of the Standard that
explicitly forbids it and demonstrated with short example why it
forbids such sintax.
Alex
Of course it would, pf was declared as a pointer-to-member of any X
>
> for the same reason it doesn't allow:
>
> pz->foo();
That's why the initialization fails, because baz isn't allowed to call foo
on any X, which is part of the type for pf.
How? I declared pf in such a way that it can only be used on Y instances,
not X or any class derived from X but not Y, such as your Z. What is the
problem?
Igor's argument applies to
void (X::*pf)() = &X::foo; // inside Y::bar
not what I wrote which is
void (Y::*pf)() = &X::foo; // inside Y::bar
My version, which may or may not be accepted by the compiler, is perfectly
typesafe.
Of course the following will certainly work and provide an identical result:
class Y : X
{
void fooshim() { X::foo(); }
public:
void foo() override;
void bar()
{
void (Y::*pf)() = &Y::fooshim;
}
};
>
> Alex
>
Why? The compiler doesn't know that pf is bound to foo. It could be
bound to some public method xyz() of X, and pz->xyz() is perfectly fine.
For example, it is legal for a class to return a pointer to its own
private method, and let the outside client call it:
class C {
void f();
public:
typedef void (C::PF)();
PF get() { return &C::f; }
};
C c;
C::PF pf = c.get();
(c*.pf)(); // c.f();
There's no access control check on assignment. As soon as &X::foo is a
valid expression, you can assign it to a variable of type void (X::*)()
without any further checks. So the compiler performs access control on
address-of, and disallows &X::foo inside Y.
Again, the compiler won't even get to the assignment part. Just writing
a no-op like
&X::foo;
will already fail to compile.
> My version, which may or may not be accepted by the compiler, is
> perfectly typesafe.
Again, nobody doubts the type safety. It's access control that's
blocking your code.
This statement should fail regardless of whether X::foo exists or not.
It's a type mismatch - the actual values are irrelevant. If it actually
compiles under some circumstances, I believe it's a compiler bug.
There's a type check on assignment, though. &X::foo inside Y would need to
be an expression of type void (Y::*)(), no further access control check
would be needed.
If the C++ standard doesn't allow it, I understand. But it would be correct
(just like generic covariance would be correct, but the C# standard doesn't
permit it).
But Y has access to the X::foo member of any Y object.
Perhaps some syntax like &Y::X::foo or &Y::__super::foo?
What do you mean, "would need to be"? Are you proposing a change to the
language? So you are saying, &X::foo should be of type void (Y::*)()
since foo is protected, but &X::xyz (where xyz is a public method of X)
should be void (X::*)(), right? I don't think there's any precedent in
C++ language for an expression to change its type depending on access to
names that participate in it. C++ tries to keep type system and access
control decoupled.
Why not simply &Y::foo ?
>Doug Harrison [MVP] <d...@mvps.org> wrote:
>> I was thinking the same thing, but it doesn't seem like the compiler
>> would let you say the following even if the hypothetical
>> initialization worked:
>>
>> (pz->*pf)();
>>
>> for the same reason it doesn't allow:
>>
>> pz->foo();
>
>Why? The compiler doesn't know that pf is bound to foo. It could be
>bound to some public method xyz() of X, and pz->xyz() is perfectly fine.
Thanks, I was wrongly focusing on the initialization, where the value of pf
was evident:
>>> void (X::*pf)() = &X::foo; // hypothetical, doesn't compile
>>> (pz->*pf)(); // same as pz->foo()
Of course, pf doesn't carry any accessibility information with it.
I beg to differ. According to the following section of the
Standard the actual type of "&Y::foo" expression is "a pointer to
foo member of X":
<quote>
5.3.1/2 Unary operators
[...] If the member is a nonstatic member of class C of type T,
the type of the result is "pointer to member of class C of typeT."
[Example:
struct A { int i; };
struct B : A { };
... &B::i ... // has type int A::*
-end example]
</quote>
The same rule is mentioned in 11.5/1 example. See the body of `fr'
function:
...
int B::* pmi_B2 = &D2::i; // OK (type of &D2::i is int B::*)
...
Alex
Interesting. So &X::foo and &Y::foo have the exact same value and the
same type (pointer-to-member-of-X), but access check fails for the
former and passes for the latter on purely syntactical grounds. But that
does seem to mean I can call a protected method on an unrelated class:
class X {
protected:
void foo();
};
class Z : public X {};
class Y : public X {
public:
void bar() {
Z z;
// z.foo(); // doesn't compile
void (X::*pf)() = &Y::foo;
(z.*pf)(); // compiles; calls z.foo()
}
};
Looks to me like a hole in the standard.
I think, there is more to it than just syntax. If `Y' actually
contains `foo' member, then "&Y::foo" yields
"pointer-to-member-of-Y" and compilation rightfully fails. So, it
seems that compiler searches starting from `Y' namespace upward
for the `foo' name. The scope where `foo' is found determines
resulting type.
> But that does seem to mean I can call a protected method on an
> unrelated class:
>
> class X {
> protected:
> void foo();
> };
>
> class Z : public X {};
>
> class Y : public X {
> public:
> void bar() {
> Z z;
> // z.foo(); // doesn't compile
> void (X::*pf)() = &Y::foo;
> (z.*pf)(); // compiles; calls z.foo()
> }
> };
>
> Looks to me like a hole in the standard.
Moreover, suppose "void (X::*pf)() = &Y::foo;" statement won't
compile. Then you could legally circumvent it with `static_cast'
(5.2.9/9):
Z z;
// z.foo(); // doesn't compile
void (Y::*pf)() = &Y::foo;
void (X::*pf2)() = static_cast<void (X::*)()>(pf);
(z.*pf2)(); // compiles; calls z.foo()
Alex
Because that is the overridden method, or possibly a
reference-to-vtable-member.