• ### Popular Now

• 11
• 9
• 10
• 9
• 10
• entries
195
198
• views
104080

337 views

Despite the fact you can't actually create new operators in C++, it's possible to create new "psuedo-operators". Warning: bad code ahead.

Let's say you're a game developer working on your first 3D engine. One of the classes you're going to write is a vector class. It might look something like:
 class Vector { public: Vector(float x = 0.0f, float y = 0.0f, float z = 0.0f) : x_(x), y_(y), z_(z) {} float x_; float y_; float z_; // other stuff excluded};float dot_product(const Vector & lhs, const Vector & rhs) { return ( (lhs.x_ * rhs.x_) + (lhs.y_ * rhs.y_) + (lhs.z_ * rhs.z_) );}Vector cross_product(const Vector & lhs, const Vector & rhs) { return Vector( (lhs.y_ * rhs.z_ - lhs.z_ * rhs.y_), (lhs.z_ * rhs.x_ - lhs.x_ * rhs.z_), (lhs.x_ * rhs.y_ - lhs.y_ * rhs.x_) );}

You'd really like to overload operators for the dot and cross product, but everyone tells you it's a honking bad idea. operator * is ambiguous, you can't get at operator . and so on.

Now what if you could turn any identifier into an operator? Let's say the dot product could be represented by dot. So if you had two vectors you could do just a dot b. Of course this generates a syntax error, consecutive identifiers aren't a happy construct in C++. So the operator needs to be made to be something other than an identifier somehow. One method is to bracket it on either side by something. %dot% or or *dot* or ^dot^ or something. Let's go with for the moment since in C++, <> brackets are often used for magic things (like templates and new style casts).

Now, what happens when the parser runs across a b? It turns it into ((a < dot) > b). So if we make dot an object of some sort, we can abuse operator overloading, and the associativity of relational operators to get this to actually compile and do what we want. And since we're already off the deep in terms of good coding practices, might as well throw in a preprocessor macro to automate the whole thing.

 template struct LtProxy { LtProxy(T r) : ref_(r) {} T ref_;};#define MAKE_OPERATOR(name, lhs_type, rhs_type, result_type, worker_function) \ const struct name ## _t {} name; \ LtProxy operator <(lhs_type lhs, name ## _t rhs) { \ return LtProxy(lhs); \ } \ result_type operator>(LtProxy & lhs, rhs_type rhs) { \ return worker_function(lhs.ref_, rhs); \ }MAKE_OPERATOR(dot, const Vector &, const Vector &, float, dot_product);MAKE_OPERATOR(cross, const Vector &, const Vector &, Vector, cross_product);int main(int, char **) { Vector a(0, 1, 3); Vector b(4, 2, 1); float dot_product = a b; std::cout << dot_product << std::endl; Vector cross_product = a b; std::cout << cross_product.x_ << std::endl; std::cout << cross_product.y_ << std::endl; std::cout << cross_product.z_ << std::endl; return 0;}

It's horribly abusive, not too maintainable, and can give operator precedence headaches, but I think it's kind of fun.

I'm tempted to put it into my new unified code library somewhere, but it violates just about every coding standard I've set for the the project. Oh well. It's immortalized here if I ever want to get at it again. :)

That's possibly the best and worst C++ code I've ever seen. :)

oh teh noes teh C++ macros ... evil. [wink]

*cough*comment leaving test*cough*

Well, I warned you guys. :) Bad code ahead and all that.