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

Wel­come back to “Strange C++.” Today we will con­tin­ue the explo­ration of C++’s point­er-to-mem­ber fea­tures, begun in Part 1 of this series, by using the .* and ->* oper­a­tors to access point­ers to mem­ber functions—that is, meth­ods.

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

Now our goal for this arti­cle is to extend our code act­ing on vec­tors: instead of just adding to an arbi­trary com­po­nent of Vector3D, we’d like to per­form an arbi­trary oper­a­tion instead. This means that along with the selec­tion of com­po­nent, we’d like to also store a selec­tion of oper­a­tion to per­form on that com­po­nent. Then we’d like to apply that oper­a­tion to a Vector3D with­out need­ing expen­sive1 con­di­tion­als or switch state­ments.

So let’s sketch up some code for how you’d solve a par­tic­u­lar prob­lem with­out using point­ers to mem­ber func­tions. Here’s a quick class that rep­re­sents an arbi­trary oper­a­tion on a Vector3D, and stores which com­po­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 mem­ber point­ers from the last arti­cle, this seems like a rea­son­able way to store the “arbi­trary com­po­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 actu­al 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 per­fect­ly nat­u­ral way to solve the prob­lem for a C++ pro­gram­mer. Just imple­ment the inter­face by over­rid­ing op() and pass­ing the com­po­nent mem­ber point­er to the par­ent con­struc­tor: easy. Note the use of Operation::componentPtr to access the “cached” com­po­nent selec­tion. This might be how you’d want to use the­se class­es:

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 out­put:

<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 class­es, isn’t there? You have to write a heck of a lot of code to account for what is essen­tial­ly one char­ac­ter of dif­fer­ence between each oper­a­tion.

In addi­tion, as a design deci­sion, shouldn’t select­ing “which com­po­nent” be sim­i­lar to select­ing “which oper­a­tion?” Why does the lat­ter require instan­ti­at­ing a new object of anoth­er sub­class of Operation, yet the for­mer requires only a call to Operation::setComponent?

To amend this, I’d like to intro­duce the mem­ber func­tion point­er. It is exact­ly what it sounds like: a point­er to a mem­ber func­tion, also known as “method.” A mem­ber func­tion point­er can point to any method that has the same para­me­ter list, return type, and con­st-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 pret­ty self-explana­to­ry, but let’s go through it any­ways. void (Example::*printMethod)() is the dec­la­ra­tion of the “printMethod” point­er vari­able. This syn­tax should be ring­ing bells in your head if you know mem­ber point­er syn­tax and func­tion point­er syn­tax. Note, how­ev­er, that we can’t use just “Example::printA” to ini­tial­ize the point­er; we need to use the address-of oper­a­tor (&) as well. I’m not entire­ly sure why there is such a dis­crep­an­cy between the syn­tax­es used for nor­mal func­tion and mem­ber func­tions. The fact that the para­me­ter list has greater prece­dence than the .* and ->* oper­a­tors leads to anoth­er unfor­tu­nate syn­tax: paren­the­ses around the class instance, .* or ->*, and the mem­ber func­tion point­er.

Now let’s try putting mem­ber func­tion point­ers to use in our Vector3D exam­ple. It’s real­ly as sim­ple as chang­ing Operation to explic­it­ly store the select­ed oper­a­tion mem­ber func­tion, rather than implic­it­ly in the case of poly­mor­phism (more on this lat­er):

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 sub­trac­tion) as meth­ods of Operation, as well as a new mem­ber, operationPtr, which points to either add or sub2. We’ve kept the op() inter­face the same, so the oth­er dif­fer­ence is how we con­struct 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… sym­met­ri­cal. We can now move inde­pen­dent­ly in the space of oper­a­tions from the space of Vector3D com­po­nents. This is known as orthog­o­nal­i­ty, which is just a fan­cy way of say­ing 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 rec­og­nize that this solu­tion is very sim­i­lar to said mech­a­nism. In fact, it’s like­ly more effi­cient. A vir­tu­al func­tion call requires deref­er­enc­ing the VPTR, look­ing up the appro­pri­ate mem­ber func­tion from VTABLE (which you’ll now rec­og­nize is sim­ply an array of mem­ber func­tion point­ers), and call­ing the point­er retrieved. This solu­tion skips straight to call­ing the mem­ber func­tion pointer—an ele­gant way to strip away unnec­es­sary func­tion­al­i­ty and code dupli­ca­tion.

So there you have it, folks. I start­ed writ­ing the­se two arti­cles when I looked online at the expla­na­tions for how to use mem­ber point­ers and noticed that every sin­gle one of them showed com­plete­ly point­less exam­ples. They all seemed to describe point­ers to mem­bers and mem­ber func­tions as an odd­i­ty, a sil­ly way to rename a class’s con­tents.

Hope­ful­ly I’ve proven them oth­er­wise and you now have an idea of how to uti­lize the­se lit­tle guys to con­struct clean­er, faster code.

Stay tuned for more “Strange C++.” May­be. Actu­al­ly, 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 con­di­tion­als can cause addi­tion­al per-ele­ment over­head when batch pro­cess­ing due to branch mis­pre­dic­tion and the sub­se­quent pipeline evic­tion, in super­scalar pipelined archi­tec­tures. []
  2. Actu­al­ly, it can point to op as well, which would cause an infinite recur­sive loop. Oops. []