C++’s .* and ->* operators: Part 2

Welcome back to “Strange C++.” Today we will continue the explo­ration of C++’s pointer-to-member features, begun in Part 1 of this series, by using the .* and ->* oper­a­tors to access point­ers to member functions—that is, meth­ods.

Last time in Part 1 we looked at how member point­ers helped us to “cache” the choice of a float member in a hypo­thet­i­cal 3D vector class, Vector3D. Then we used this member pointer stor­ing the choice of compo­nent to add a float to an arbi­trary compo­nent of a std::vector of Vector3Ds.

Now our goal for this arti­cle is to extend our code acting on vectors: instead of just adding to an arbi­trary compo­nent of Vector3D, we’d like to perform an arbi­trary oper­a­tion instead. This means that along with the selec­tion of compo­nent, we’d like to also store a selec­tion of oper­a­tion to perform on that compo­nent. Then we’d like to apply that oper­a­tion to a Vector3D with­out need­ing expen­sive1 condi­tion­als or switch state­ments.

So let’s sketch up some code for how you’d solve a partic­u­lar prob­lem with­out using point­ers to member func­tions. Here’s a quick class that repre­sents an arbi­trary oper­a­tion on a Vector3D, and stores which compo­nent to oper­ate on:

// Represents an operation done against one component of a Vector3D
class Operation {
protected:
	float Vector3D::*componentPtr; // store the component selection using a member pointer

public:
	Operation(float Vector3D::* const componentPtr) :
		componentPtr(componentPtr) {
	}

	void setComponent(float Vector3D::* const componentPtr) {
		this->componentPtr = componentPtr;
	}

	virtual void op(Vector3D &vector3D, float f) const = 0; // interface to use the operation
};

Given the require­ments and what we know about member point­ers from the last arti­cle, this seems like a reason­able way to store the “arbi­trary compo­nent” selec­tion in a class. To real­ize the “arbi­trary oper­a­tions” part of the deal, you’d have to derive from this class into the actual oper­a­tions, in a text­book exam­ple of poly­mor­phism:

class OperationAdd : public Operation {
public:
	OperationAdd(float Vector3D::* const componentPtr) :
		Operation(componentPtr) {
	}

	void op(Vector3D &vector3D, float f) const {
		vector3D.*componentPtr += f; // specialize the operation as addition
	}
};

class OperationSub : public Operation {
public:
	OperationSub(float Vector3D::* const componentPtr) :
		Operation(componentPtr) {
	}

	void op(Vector3D &vector3D, float f) const {
		vector3D.*componentPtr -= f; // specialize the operation as subtraction
	}
};

Now, using poly­mor­phism is a perfectly natural way to solve the prob­lem for a C++ program­mer. Just imple­ment the inter­face by over­rid­ing op() and pass­ing the compo­nent member pointer to the parent construc­tor: easy. Note the use of Operation::componentPtr to access the “cached” compo­nent selec­tion. This might be how you’d want to use these classes:

void applyOperation(std::vector<Vector3D> &vs, const Operation &operation, float amount) {
	for(std::vector<Vector3D>::iterator v_i = vs.begin() ; v_i != vs.end() ; v_i++) {
		operation.op(*v_i, amount); // apply the operation to every Vector3D
	}
}

int main() {
	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

	OperationAdd vectorAdd(&Vector3D::z); // create an "addition to .z" operation
	std::cout << "Adding to component z..." << std::endl;
	applyOperation(vs, vectorAdd, 1.f); // apply "addition of 1 to .z" to all vectors
	std::copy(vs.begin(), vs.end(), std::ostream_iterator<Vector3D>(std::cout, " "));
	std::cout << std::endl; // print out the list again

	OperationSub vectorSub(&Vector3D::y); // create an "subtraction from .y" operation
	std::cout << "Subtracting from component y..." << std::endl;
	applyOperation(vs, vectorSub, 3.f); // apply "subtraction of 3 from .y" to all vectors
	std::copy(vs.begin(), vs.end(), std::ostream_iterator<Vector3D>(std::cout, " "));
	std::cout << std::endl; // print out the list again

	return EXIT_SUCCESS;
}

Exam­ple output:

<0, 2, 3> <0, 2, 3> <0, 2, 3> <0, 2, 3> <0, 2, 3>
Adding to component z...
<0, 2, 4> <0, 2, 4> <0, 2, 4> <0, 2, 4> <0, 2, 4>
Subtracting from component y...
<0, -1, 4> <0, -1, 4> <0, -1, 4> <0, -1, 4> <0, -1, 4>

But there’s a lot of code dupli­ca­tion for the OperationAdd and OperationSub classes, isn’t there? You have to write a heck of a lot of code to account for what is essen­tially one char­ac­ter of differ­ence between each oper­a­tion.

In addi­tion, as a design deci­sion, shouldn’t select­ing “which compo­nent” be simi­lar to select­ing “which oper­a­tion?” Why does the latter require instan­ti­at­ing a new object of another subclass of Operation, yet the former requires only a call to Operation::setComponent?

To amend this, I’d like to intro­duce the member func­tion pointer. It is exactly what it sounds like: a pointer to a member func­tion, also known as “method.” A member func­tion pointer can point to any method that has the same para­me­ter list, return type, and const-ness as spec­i­fied in the pointer’s type. Let’s see an exam­ple:

class Example {
public:
	void printA() {
		std::cout << 'A' << std::endl;
	}

	void printB() {
		std::cout << 'B' << std::endl;
	}

	static void printC();
};

void Example::printC() {
	std::cout << 'C' << std::endl;
}

void printD() {
	std::cout << 'D' << std::endl;
}

int main() {
	// member function pointers:
	Example example;
	void (Example::*printMethod)() = &Example::printA; // declare a pointer to printA
	(example.*printMethod)(); // prints 'A'
	printMethod = &Example::printB; // reassign pointer to printB
	(example.*printMethod)(); // prints 'B'

	// regular function pointers:
	// even though printC is part of Example, a regular function will do
	void (*printFunc)() = Example::printC; // see how the "&" isn't needed
	printFunc(); // prints C
	printFunc = printD; // here too, "&" is omitted
	printFunc(); // prints D
}

I think it’s pretty self-explana­tory, but let’s go through it anyways. void (Example::*printMethod)() is the decla­ra­tion of the “printMethod” pointer vari­able. This syntax should be ring­ing bells in your head if you know member pointer syntax and func­tion pointer syntax. Note, however, that we can’t use just “Example::printA” to initial­ize the pointer; we need to use the address-of oper­a­tor (&) as well. I’m not entirely sure why there is such a discrep­ancy between the syntaxes used for normal func­tion and member func­tions. The fact that the para­me­ter list has greater prece­dence than the .* and ->* oper­a­tors leads to another unfor­tu­nate syntax: paren­the­ses around the class instance, .* or ->*, and the member func­tion pointer.

Now let’s try putting member func­tion point­ers to use in our Vector3D exam­ple. It’s really as simple as chang­ing Operation to explic­itly store the selected oper­a­tion member func­tion, rather than implic­itly in the case of poly­mor­phism (more on this later):

class Operation {
protected:
	// a pointer to a const member function with params (Vector3D &, float) returning void
	void (Operation::*operationPtr)(Vector3D &, float) const;
	// store the component selection using a member pointer
	float Vector3D::*componentPtr;

public:
	Operation(void(Operation::*operationPtr)(Vector3D &, float) const, float Vector3D::* const componentPtr) :
		operationPtr(operationPtr), componentPtr(componentPtr) {
	}

	void setComponent(float Vector3D::* const componentPtr) {
		this->componentPtr = componentPtr;
	}

	void setOperation(void(Operation::* const operation)(Vector3D &, float) const) {
		this->operationPtr = operation;
	}

	void op(Vector3D &vector3D, float f) const {
		(this->*operationPtr)(vector3D, f); // call the member (add or sub)
	}

	void add(Vector3D &vector3D, float f) const {
		vector3D.*componentPtr += f;
	}

	void sub(Vector3D &vector3D, float f) const {
		vector3D.*componentPtr -= f;
	}
};

All we’ve done here is add the two exam­ple oper­a­tions (addi­tion and subtrac­tion) as meth­ods of Operation, as well as a new member, operationPtr, which points to either add or sub2. We’ve kept the op() inter­face the same, so the other differ­ence is how we construct the Operation instance and how to change the oper­a­tion selec­tion:

Operation vectorOp(&Operation::add, &Vector3D::z); // create an "addition to .z" operation
applyOperation(vs, vectorOp, 1.f); // apply "addition of 1 to .z" to all vectors

vectorOp.setOperation(&Operation::sub); // note the use of & operator
vectorOp.setComponent(&Vector3D::y);
applyOperation(vs, vectorOp, 3.f); // apply "subtraction of 3 from .y" to all vectors

Note how the lines of vectorOp.setOperation(&Operation::sub) and vectorOp.setComponent(&Vector3D::y) look so… symmet­ri­cal. We can now move inde­pen­dently in the space of oper­a­tions from the space of Vector3D compo­nents. This is known as orthog­o­nal­ity, which is just a fancy way of saying that this code feels nice to use.

Now about the tech­ni­cals: if you under­stand the mech­a­nism behind poly­mor­phism, you’ll recog­nize that this solu­tion is very simi­lar to said mech­a­nism. In fact, it’s likely more effi­cient. A virtual func­tion call requires deref­er­enc­ing the VPTR, look­ing up the appro­pri­ate member func­tion from VTABLE (which you’ll now recog­nize is simply an array of member func­tion point­ers), and call­ing the pointer retrieved. This solu­tion skips straight to call­ing the member func­tion pointer—an elegant way to strip away unnec­es­sary func­tion­al­ity and code dupli­ca­tion.

So there you have it, folks. I started writ­ing these two arti­cles when I looked online at the expla­na­tions for how to use member point­ers and noticed that every single one of them showed completely point­less exam­ples. They all seemed to describe point­ers to members and member func­tions as an oddity, a silly way to rename a class’s contents.

Hope­fully I’ve proven them other­wise and you now have an idea of how to utilize these little guys to construct cleaner, faster code.

Stay tuned for more “Strange C++.” Maybe. Actu­ally, don’t count on it.

You should read “C++’s .* and ->* oper­a­tors: Part 1” if you haven’t already.

  1. “Expen­sive” mean­ing condi­tion­als can cause addi­tional per-element over­head when batch process­ing due to branch mispre­dic­tion and the subse­quent pipeline evic­tion, in super­scalar pipelined archi­tec­tures. []
  2. Actu­ally, it can point to op as well, which would cause an infi­nite recur­sive loop. Oops. []