- Published:
- 07.26.2010 / 10pm
- Category:
- Work
- Previous:
- C++’s .* and ->* operators: Part 2
- Next:
- Path of the Wastrel
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 epic drama “I Am Now a Management Major”), so here’s another filler post. I stopped being serious since the first post, so enjoy the troll.
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 float
s? 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 typedef
s. 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.