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

Well, none of my drafts are yet worth putting up (preview: the first part of a long series on color theory, and a follow-up to the acad­e­mic advis­ing epic drama “I Am Now a Manage­ment Major“), so here’s another filler post. I stopped being seri­ous since the first post, so enjoy the troll.

Welcome back, and thanks for pick­ing up this April Fools’ issue of “@#$%ing Strange C++: I hate this language sooo much.” Today, let’s take our exam­ple code from last time and apply some text­book tech­niques to screw it u—improve its read­abil­ity and reusabil­ity.

Why would anyone do this with exam­ple code writ­ten to show an entirely awful concept with an entirely contrived exam­ple? I don’t know.

Let’s backpedal to the impor­tant part: Vector3D. Hey, we named it Vector3D, so why does it only allow floats? Why doesn’t it do any vector-y stuff? LET US ADD SOME FUNCTIONALITY WHICH WE WILL NOT USE.

// HELL YEAH, LET'S TEMPLATE IT
template<typename Component_T = double> // STICK IN A DEFAULT, EVEN IF IT DOESN'T MAKE SENSE
class Vector3D {
public:
	typedef Component_T ComponentType; // YOU'LL SEE WHY WE NEED THIS

	Component_T x;
	Component_T y;
	Component_T z;

	// Component_T MIGHT BE A BIGNUM CLASS OR SOMETHING, SO WE PASS CONST REFERENCES
	Vector3D(const Component_T &x, const Component_T &y, const Component_T &z) :
		x(x), y(y), z(z) {
	}

Mmm-hmm, templates are a sure part of any messy C++ recipe. Once you learn them, you’ll never remem­ber how to program well again. Let’s continue on to the oper­a­tor over­loads. We completely ignore the fact that infix nota­tion makes no sense in a language that features prefix for every­thing else, or that these are all really construc­tors, so they’re better off as construc­tors in the “named construc­tors” idiom, or that nobody ever follows the same conven­tions for these “arith­metic” oper­a­tors; we’re using them because they’re snazzy little bits of sugar coat­ing.

	// OPERATOR OVERLOADS ALL OVER THE PLACE
	friend Vector3D operator-(const Vector3D &a) {
		return Vector3D(-a.x, -a.y, -a.z);
	}

	friend Vector3D operator+(const Vector3D &a, const Vector3D &b) {
		return Vector3D(a.x + b.x, a.y + b.y, a.z + b.z);
	}

	friend Vector3D operator-(const Vector3D &a, const Vector3D &b) {
		return Vector3D(a.x - b.x, a.y - b.y, a.z - b.z);
	}

	// WE DON'T CARE THAT SOMETIMES PEOPLE WANT THE INNER (DOT) PRODUCT
	friend Vector3D operator*(const Vector3D &a, const Vector3D &b) {
		return Vector3D(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
	}

	friend Vector3D operator*(const Vector3D &a, const Component_T &s) {
		return Vector3D(a.x * s, a.y * s, a.z * s);
	}

	// LOLOL REDUNDANT
	friend Vector3D operator*(const Component_T &s, const Vector3D &a) {
		return Vector3D(a.x * s, a.y * s, a.z * s);
	}

	friend Vector3D operator/(const Vector3D &a, const Component_T &s) {
		return Vector3D(a.x / s, a.y / s, a.z / s);
	}

	// BECAUSE a.dot(b) IS LIKE THE OTHER OPERATORS, NOT BECAUSE INFIX IS GOOD
	Vector3D dot(const Vector3D &a) {
		return Vector3D(x * a.x, y * a.y, z * a.z);
	}

	friend std::ostream &operator<<(std::ostream &os, const Vector3D &v) {
		return os << '<' << v.x << ", " << v.y << ", " << v.z << '>';
	}
};

Alright, now that we have blinged out Vector3D for no appar­ent reason, let’s extend our Operation cla—oh crud, there’s unqual­i­fied types need­ing the typename keyword, template para­me­ters, and angled brack­ets all over the place. What to do? We’ll typedef every­thing away, of course.

template<typename Vector3D_T> // DAMN, NOW WE HAVE TO TEMPLATE THIS
class Operation: public std::binary_function<Vector3D_T, typename Vector3D_T::ComponentType, void> {
protected:
	// WE NEED typename TO FULLY QUALIFY; DON'T ASK WHY
	typedef typename Vector3D_T::ComponentType ComponentType;
	// (ROYAL) WE DON'T FEEL LIKE TYPING THESE ANY MORE
	typedef void (Operation::*OperationPointerType)(Vector3D_T &, const ComponentType &) const;
	typedef ComponentType Vector3D_T::*ComponentPointerType;

	// a pointer to a const member function with params (Vector3D &, float) returning void
	OperationPointerType operationPtr;
	// store the component selection using a member pointer
	ComponentPointerType componentPtr;

Just for the hell of it, let’s derive from std::binary_function to pollute our class name­space and let us do weird, fancy things with argu­ment bind­ing in <functional> and algo­rithms from <algorithm>, <numeric>, etc.

public:
	Operation(OperationPointerType const operationPtr, ComponentPointerType const componentPtr) :
		operationPtr(operationPtr), componentPtr(componentPtr) {
	}

	void setComponent(ComponentPointerType const componentPtr) {
		this-&gt;componentPtr = componentPtr;
	}

	void setOperation(OperationPointerType const operation) {
		this-&gt;operationPtr = operation;
	}

I guess it’s a little better with the typedefs. But here we go with the operator() over­load that makes this a proper std::binary_function:

	// HELL YEAH, LET'S MAKE THIS A FUNCTOR
	void operator()(Vector3D_T &amp;vector3D, const ComponentType &amp;x) const {
		(this-&gt;*operationPtr)(vector3D, x);
	}

	void add(Vector3D_T &amp;vector3D, const ComponentType &amp;x) const {
		vector3D.*componentPtr += x;
	}

	void sub(Vector3D_T &amp;vector3D, const ComponentType &amp;x) const {
		vector3D.*componentPtr -= x;
	}
};

Wait a minute. This almost the same thing as from the arti­cle, just all weird look­ing. Feel­ing duped? You should be. Templat­ing will make you feel all empty inside like that. But let’s see what we can do with this now:

int main() {
	std::vector&lt;Vector3D&lt;float&gt; &gt; vs(5, Vector3D&lt;float&gt; (0, 2, 3)); // create five &lt;0, 2, 3&gt;s
	std::copy(vs.begin(), vs.end(), std::ostream_iterator&lt;Vector3D&lt;float&gt; &gt;(std::cout, &quot; &quot;));
	std::cout &lt;&lt; std::endl; // print out the list

In case you haven’t noticed yet, that’s work­ing code for writ­ing stuff to a stream; you can actu­ally get an output iter­a­tor from a std::ostream and copy into it as with any other iter­a­tor.

std::cout &lt;&lt; &quot;Negating vectors...&quot; &lt;&lt; std::endl;
	std::transform(vs.begin(), vs.end(), vs.begin(), std::negate&lt;Vector3D&lt;float&gt; &gt;());
	std::copy(vs.begin(), vs.end(), std::ostream_iterator&lt;Vector3D&lt;float&gt; &gt;(std::cout, &quot; &quot;));
	std::cout &lt;&lt; std::endl; // print out the list

WHOA MAN, THAT’S A FANCY NEGATE. std::negate creates a class fitting the unary_operator concept, which is a func­tor of arity one. Specif­i­cally, it uses the templated class’s operator- in the func­tor to return the nega­tive of what­ever is passed to the func­tor.

More fanci­ness:

	Operation&lt;Vector3D&lt;float&gt; &gt; vectorOp(&amp;Operation&lt;Vector3D&lt;float&gt; &gt;::add, &amp;Vector3D&lt;float&gt;::z);
	std::cout &lt;&lt; &quot;Adding to component z...&quot; &lt;&lt; std::endl;
	std::for_each(vs.begin(), vs.end(), std::bind2nd(vectorOp, 1.f));
	std::copy(vs.begin(), vs.end(), std::ostream_iterator&lt;Vector3D&lt;float&gt; &gt;(std::cout, &quot; &quot;));
	std::cout &lt;&lt; std::endl; // print out the list again

	vectorOp.setOperation(&amp;Operation&lt;Vector3D&lt;float&gt; &gt;::sub); // note the use of &amp; operator
	vectorOp.setComponent(&amp;Vector3D&lt;float&gt;::y);
	std::cout &lt;&lt; &quot;Subtracting from component y...&quot; &lt;&lt; std::endl;
	std::for_each(vs.begin(), vs.end(), std::bind2nd(vectorOp, 3.f));
	std::copy(vs.begin(), vs.end(), std::ostream_iterator&lt;Vector3D&lt;float&gt; &gt;(std::cout, &quot; &quot;));
	std::cout &lt;&lt; std::endl; // print out the list again

	return EXIT_SUCCESS;
}

Yes, there I am actu­ally “bind­ing” a fixed value to the second para­me­ter of my func­tor, some­thing which required the use of std::binary_function to work. There is a better alter­na­tive that doesn’t require this, nor restrict us to binary (arity-two) func­tors, nor require us to spec­ify the para­me­ter to bind in such a cumber­some way. It’s called boost::bind, and you’re not going to use it because you’ll discover that boost::lambda is better in every regard, and that you’ll be turned off by having to stick Boost’s bulk into code you found on a blog.

Next steps? Well, we should docu­ment the hell out of this. Mmm, yeah, let’s bring in some Doxy­gen, or even better, some light­weight boutique docu­men­ta­tion gener­a­tor! Let’s write some embed­ded docu­menta… eh, we’re too lazy.