I stumbled across this question, which had an answer that used an odd construct:
typedef std::queue<int> Q;
typedef Q::container_type C;
C & get (Q &q)
{
struct hack : private Q {
static C & get (Q &q) {
return q.*&hack::c;
}
};
return hack::get(q);
}
I generally follow that q
has access to its own c
member that is being referenced by the get
function. But, I am at a loss to clearly explain it. What is happening exactly with the .*&
, and why is it allowed?
In English, the symbol * is generally called asterisk. Depending on the context, the asterisk symbol has different meanings. In Math, for instance, the asterisk symbol is used for multiplication of two numbers, let's say 4 * 5; in this case, the asterisk is voiced 'times,' making it “4 times 5”.
Asterisk. Meaning: You're afraid the person isn't as cool as you. The main reason people use asterisks in a text is to censor a word, for example: "I like deep-fried sandwiches so my friends call me the C*** of Monte Cristo.
noun. a small starlike symbol (*), used in writing and printing as a reference mark or to indicate omission, doubtful matter, etc. Linguistics. the figure of a star (*) used to mark an utterance that would be considered ungrammatical or otherwise unacceptable by native speakers of a language, as in * I enjoy to ski.
“Asterisk is also used in spreadsheet formulas as the mathematical 'times' symbol rather than the instinctive X.” That is something that came way after the real origin. ” * ” has long meant multiplication in real programming languages such as FORTRAN, BASIC, and PASCAL.
typedef std::queue<int> Q;
Q
is a queue
adapted container.
typedef Q::container_type C;
C
is the underlying container of the Q
-- which is a deque<int>
.
C & get (Q &q) {
get
takes a queue
and returns a deque
. In fact it returns the deque
that the queue
wraps: by conventional means, this is not possible.
struct hack : private Q {
hack
is a type local to the function. It inherits from Q
and has only one static member function. From its name, you may suspect it is a hack. You are right.
No hack
is ever instantiated.
static C & get (Q &q) {
hack::get
has the same signature as get
itself. In fact we delegate all of the work of get
to this method.
return q.*&hack::c;
this line needs to be broken down. I will do it in more lines:
using mem_ptr_t = C Q::*; // aka typedef C Q::*mem_ptr_t;
mem_ptr_t c_mem_ptr = &hack::c;
C& ret = q.*c_mem_ptr;
return ret;
The first line defines the type of a member pointer to a field of type C
within a Q
. Both the C++11 and C++03 ways of naming this type are ugly.
The second line gets a member pointer to the field c
in Q
. It does this through the hole in the type system of C++. &hack::c
is logically of type C hack::*
-- a pointer to a member of type C
within a class of type hack
. In fact, that is why we can access it in a static
member of hack
. But the c
in question is actually in Q
, so the actual type of the expression in C++ is C Q::*
: a pointer to a member variable of Q
.
You cannot directly get this member pointer within hack
-- &Q::c
is illegal, but &hack::c
is not.
You can think of member pointers as 'typed offsets' into another type: &hack::c
is the "offset" of c
within Q
together with knowing it is of type C
. Now this isn't really true -- it is some opaque value that tells the compiler how to get c
from Q
-- but it helps to think about it that way (and it may be implemented that way in simple cases).
We then use this member pointer together with a Q&
to get the c
out of the Q
. Getting a member pointer is constrained by protected: using it is not! The way we do it is with operator .*
, which is the member dereference operator, which you can pass either member function pointers or members on the right, and class instances on the left.
instance .* member_ptr
is an expression that finds the member "pointed to" by member_ptr
within the instance
. In the original code, everything was done on one line:
instance .* &class_name::member_name
so it looked like there was an operator .*&
.
}
};
and then we close up the static method and hack
class, and:
return hack::get(q);
}
call it. This technique gives access to protected
state: without it, protected
members can only be accessed in child classes of the same instance. Using this, we can access protected
members of any instance, without violating any bit of the standard.
It's a hack, as the nomenclature indicates.
.*
takes an object on the left side, and a member pointer on the right side, and resolves the pointed-to member of the given object. &
is, of course, the referencing operator; &Class::Member
returns a member pointer, which cannot by itself be dereferenced but which can be used with the .*
and ->*
operators (the latter being the wackiest of all C++ operators). So obj .* &Class::Member
has exactly the same effect as obj.Member
.
The reason this more complicated version is being used comes down to a loophole in protection semantics; basically, it allows access to protected
members of a base class object, even if the object is not of the same type as the class doing this dirty hack.
Personally, I think the trick is too clever by half. I'd ordinarily* write such code as:
struct hack : private Q {
static C & get (Q &q) {
return static_cast<hack &>(q).c;
}
};
Which is technically slightly less safe, but doesn't obscure what's going on.
.* Well, ordinarily I'd avoid writing such a thing at all. But I literally did this earlier today, so I can't really throw stones.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With