C++‘s .* and ->* operators: Part 1
Welcome to the newest installment of “Strange C++.” Today we’ll be discussing the .*
and ->*
operators in C++.
If you’re like me, then you probably spend all of your free time reading C++ FAQ Lite for some reason. Of course I don’t do that, because that’d be kind of weird and antisocial, but I figure people like me are probably weird and antisocial. Let’s be modest here.
At any rate, you’d have come across the rules for which C++ operators can and can’t be overloaded. Those in the “can’t” camp are the ternary operator ?:
, the scope spec operator ::
, the member-of operator .
, and finally the .*
operator1. What the Bjarne Stroustrup is that?
In few words, the .*
operator accesses an object’s member pointed to by a member pointer. The ->*
pointer does the same thing, only it accesses the member of a pointer to an object, not an object or its reference. Unfortunately, this does nothing to clarify the operator’s use and introduces the additional question: what is a member pointer? This is the heart of the article, and I will explain through examples.
Let’s say we had the following class, representing a vector in R3:
class Vector3D { public: float x; float y; float z; Vector3D(float x, float y, float z) : x(x), y(y), z(z) { } friend std::ostream &operator<<(std::ostream &os, const Vector3D &v) { return os << '<' << v.x << ", " << v.y << ", " << v.z << '>'; } };
This is how you could use member pointers, taking note of the use of the .*
operator:
int main() { Vector3D v(0, 2, 3); std::cout << v << std::endl; // output: <0, 2, 3> float Vector3D::*componentPtr = &Vector3D::x; // member pointer to the x member v.*componentPtr += 1.f; // "dereference" it against instance v and add 1.f to it std::cout << v << std::endl; // output: <1, 2, 3> componentPtr = &Vector3D::y; // now point it to the y member v.*componentPtr *= 2.f; // "dereference" it against instance v and mul it by 2.f std::cout << v << std::endl; // output: <1, 4, 3> return EXIT_SUCCESS; }
Now you might say: “Well, that looks like a whole lot of gibberish symbols to me. What does it mean and what does it do?”
Well, first we’re declaring and initializing a variable called componentPtr
. What componentPtr
does is point to a member of Vector3D
of the type float. Take a cue from its declaration syntax: note that the asterisk, indicating a pointer (or indirection), comes after “Vector3D::
” in “float Vector3D::*componentPtr
.” Take this to mean that it’s a pointer that can move within the space of the Vector3D::
scope.
Also take another clue from the initialization: it’s initialized to &Vector3D::x
—the address of the x member in any Vector3D
instance, not say, &v.x
. So, when we use the pointer later, we need to specify which Vector3D
instance it goes with: v.*componentPtr
Aside
I think of member pointers as offsets from the “this
” pointers of instances. Imagine each instance as an instance of a packed POD (Plain Ol’Data) structure that contains two 32-bit floats, x then y, and a pointer to the structure points to its beginning. So, v.y
would be the same thing as *static_cast<float *>(static_cast<uintptr_t>(&v) + sizeof(float))
and v.x
would be *static_cast<float *>(&v)
. Indeed, that expression will work on many compilers (though you obviously shouldn’t use it).
Thus, I imagine componentPtr
to be the “+ sizeof(float)
” constant offset that marks how to use the object’s address to access the desired member. If you didn’t get any of the last two paragraphs, just pretend you didn’t read them, because it’s not too important.
End aside
So now you see how to use member pointers and how they work. But you’re probably thinking, “Xo, what the heck would compel any sane C++ programmer to use member pointers? In your last example, why couldn’t someone just use v.x
and v.y
instead of taking a pointer to them?”
Well first off, there aren’t really any sane C++ programmers. If you’re thinking that you could be an exception to this, you’re probably not sane or you might not be a C++ programmer. Maybe both.
Secondly, the idea behind them is that they’re variables. You use them the same way you would any other variable: to store results that you’ve computed or to pass data around. This means if you can boil down a long set of computations into a choice between members of a class, then you can avoid needing to compute it repeatedly and instead just pass a member pointer around.
Let’s see an example. We’re trying to write a function that takes in a std::vector
of Vector3D
s and adds a float to a component of each one. Now, which component is determined at runtime, so we have to pass it as a parameter to the function:
void add(std::vector<Vector3D> & vs, int componentSelect, float amount) { for(std::vector<Vector3D>::iterator v_i = vs.begin() ; v_i != vs.end() ; v_i++) { switch(componentSelect) { // switch to determine which component to modify case 0: v_i->x += amount; break; case 1: v_i->y += amount; break; case 2: v_i->z += amount; break; default: break; } } } int main() { int componentSelect; // 0 means .x, 1 means .y, 2 means .z componentSelect = std::rand() % 3; // make a decision as to which one std::vector<Vector3D> vs(5, Vector3D(0, 2, 3)); // create five <0, 2, 3>s std::copy(vs.begin(), vs.end(), std::ostream_iterator<Vector3D>(std::cout, " ")); std::cout << std::endl; // print out the list add(vs, componentSelect, 1.f); // add 1.f to a component of each Vector3D std::copy(vs.begin(), vs.end(), std::ostream_iterator<Vector3D>(std::cout, " ")); std::cout << std::endl; // print out the list again return EXIT_SUCCESS; }
Example output:
<0, 2, 3> <0, 2, 3> <0, 2, 3> <0, 2, 3> <0, 2, 3> <0, 2, 4> <0, 2, 4> <0, 2, 4> <0, 2, 4> <0, 2, 4>
The above naïve way involves this int
→Vector3D
component conversion for each element in the container. This is slow and involves a lot of code copying & pasting. Moving the switch statement outside of the loop helps with the slow, but makes the code look even worse.
Member pointers to the rescue:
void add(std::vector<Vector3D> & vs, int componentSelect, float amount) { static float Vector3D::* const componentPtrs[3] = {&Vector3D::x, &Vector3D::y, &Vector3D::z}; float Vector3D::*componentPtr = componentPtrs[componentSelect]; // select from array for(std::vector<Vector3D>::iterator v_i = vs.begin() ; v_i != vs.end() ; v_i++) { *v_i.*componentPtr += amount; // note: very little code duplication } }
Now you see that we can use member pointers to store the result of the component selection (the switch statement) and then operate on data using this stored result.
Note that I used *v_i.*componentPtr += amount;
instead of v_i->*componentPtr += amount;
. This is because v_i
is a STL iterator
, not a Vector3D *
. This is where you see the ->*
operator (indirect .*
) diverge from dereference-then-.*
, as iterators
do not need to define a ->*
operator but do need to define a dereference (*
) operator. If you really want to use ->*
(and who doesn’t?), you’ll need to convert the iterator to a pointer first:
for(std::vector<Vector3D>::iterator v_i = vs.begin() ; v_i != vs.end() ; v_i++) { Vector3D *v = &*v_i; v->*componentPtr += amount; // note the use of ->* }
This is isn’t even the only way we could have done this: there are also a few tricks with templates or STL components like <functional>
we could have used to the same effect, with varying amounts of ease-of-use and compile-time vs. run-time flexibility.
This is just another example of the bewildering flexibility C++ offers between performance and code readability, or if you like, between compile-time code execution and run-time code execution.
Stay tuned for more “Strange C++.” In the next issue, we’ll discuss using member pointers to point to member functions, and how exactly using them will benefit our C++ applications.
You should read “C++‘s .* and ->* operators: Part 2.”
- Of course, this is not including the indirect versions of the latter two,
->
and->*
[▲]