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 academic advising drama “I Am Now a Management Major“), so here’s another filler post.

Welcome back, and thanks for picking up this April Fools’ issue of “@#$%ing Strange C++: I hate this language sooo much.” Today, let’s take our example code from last time and apply some textbook techniques to screw it u—improve its readability and reusability.

Why would anyone do this with example code written to show an entirely awful concept with an entirely contrived example? I don’t know.

Let’s backpedal to the important 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 remember how to program well again. Let’s continue on to the operator overloads. We completely ignore the fact that infix notation makes no sense in a language that features prefix for everything else, or that these are all really constructors, so they’re better off as constructors in the “named constructors” idiom, or that nobody ever follows the same conventions for these “arithmetic” operators; we’re using them because they’re snazzy little bits of sugar coating.

	// 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 apparent reason, let’s extend our Operation cla—oh crud, there’s unqualified types needing the typename keyword, template parameters, and angled brackets all over the place. What to do? We’ll typedef everything 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 namespace and let us do weird, fancy things with argument binding in <functional> and algorithms from <algorithm>, <numeric>, etc.

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

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

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

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

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

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

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

Wait a minute. This almost the same thing as from the article, just all weird looking. Feeling duped? You should be. Templating will make you feel all empty inside like that. But let’s see what we can do with this now:

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

In case you haven’t noticed yet, that’s working code for writing stuff to a stream; you can actually get an output iterator from a std::ostream and copy into it as with any other iterator.

std::cout << "Negating vectors..." << std::endl;
	std::transform(vs.begin(), vs.end(), vs.begin(), std::negate<Vector3D<float> >());
	std::copy(vs.begin(), vs.end(), std::ostream_iterator<Vector3D<float> >(std::cout, " "));
	std::cout << 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 functor of arity one. Specifically, it uses the templated class’s operator- in the functor to return the negative of whatever is passed to the functor.

More fanciness:

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

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

	return EXIT_SUCCESS;
}

Yes, there I am actually “binding” a fixed value to the second parameter of my functor, something which required the use of std::binary_function to work. There is a better alternative that doesn’t require this, nor restrict us to binary (arity-two) functors, nor require us to specify the parameter to bind in such a cumbersome 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 document the hell out of this. Mmm, yeah, let’s bring in some Doxygen, or even better, some lightweight boutique documentation generator! Let’s write some embedded documenta… eh, we’re too lazy.


About this entry