I have seen this technique, that I refer to as "protected hack", mentioned quite a few times here and elsewhere. Yes, this behavior is correct and it is indeed a legal way to circumvent protected access without resorting to any "dirty" hacks.
When m
is member of class Base
, then the problem with making the &Derived::m
expression to produce a pointer of Derived::*
type is that class member pointers are contravariant, not covariant. It would make the resultant pointers unusable with Base
objects. For example, this code compiles
struct Base { int m; };
struct Derived : Base {};
int main() {
int Base::*p = &Derived::m; // <- 1
Base b;
b.*p = 42; // <- 2
}
because &Derived::m
produces an int Base::*
value. If it produced a int Derived::*
value, the code would fail to compile at line 1. And if we attempted to fix it with
int Derived::*p = &Derived::m; // <- 1
it would fail to compile at line 2. The only way to make it compile would be to perform a forceful cast
b.*static_cast<int Base::*>(p) = 42; // <- 2
which is not good.
P.S. I agree, this is not a very convincing example ("just use &Base:m
from the beginning and the problem is solved"). However, http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#203 has more info that sheds some light on why such decision was made originally. They state
Notes from 04/00 meeting:
The rationale for the current treatment is to permit the widest possible use to be made of a given address-of-member expression. Since a pointer-to-base-member can be implicitly converted to a pointer-to-derived-member, making the type of the expression a pointer-to-base-member allows the result to initialize or be assigned to either a pointer-to-base-member or a pointer-to-derived-member. Accepting this proposal would allow only the latter use.