computer science goes bonk / all posts / rss / about

squirrely operators

Sometimes C++ programmers come up with...unexpected...solutions to problems.

I'm not talking about IOCCC submissions, which are frighteningly awesome. What I'm talking about are three (arguably) useful idioms that you simply don't see very often:

 

Even if these "operators" are a little too squirrely for you, it's useful to be able to recognize them when you encounter them in the wild. Let's look at them one by one:

Goes-to (-->) operator

The first operator is --> (aka "goes-to"). It might appear in a situation like this:

int x = 100;
while (x --> 0) {
   std::cout << x << std::endl;
}

What does that code do? It displays the numbers 99 through 0.

If you look at any C++ operator precedence chart, you won't see --> anywhere. That's because the "goes-to" operator isn't really an operator. It's two operators (-- and >) combined with some unfortunate spacing. How cute.

int x = 100;
while (x-- > 0) {    // with normal spacing
   std::cout << x << std::endl;
}

Verdict: It's a little ironic that an expression that actually looks like what it does could be harder for programmers to parse than a more typical expression. That combined with the fact that there's nothing functionally different about this syntax relegates --> to the domain of "geek party tricks".

Bang-bang (!!) operator

The !! (aka bang-bang or double-bang operator) is just two unary negation operators applied in quick succession. It might look like this:

Foo f;
if (!!f) {
   // ...
}

This operator is usually used to coerce a boolean value out of some type, although it's been said that !! was used in C to map arbitrary numbers to 0 or 1 for use as indices into arrays of size two.

Wait a minute, you shout angrily. What's wrong with operator bool? Well, adding a generic conversion to bool can add unexpected bonus behavior to a type:

#include <iostream>

class Foo {
   bool m_b;
public:
   explicit Foo(bool b) : m_b(b) {}
   operator bool() const { return m_b; }
};

int main() {
   Foo f(true);
   if (!f) std::cout << "f is false" << std::endl; // sure
   if (f) std::cout << "f is true" << std::endl;   // makes sense
   int i = f;              // wait a minute
   f << 1;                 // what?
   if (f < Foo(false)) ... // um...
}

All of those expressions involving a Foo are completely valid, given that Foo has an operator bool.

So how can we get our conversion to bool to be a little less promiscuous? One answer involves overloading operator! and using !!:

#include <iostream>

class Foo {
   bool m_b;
public:
   explicit Foo(bool b) : m_b(b) {}
   bool operator!() const { return !m_b; }
};

int main() {
   Foo f(true);
   if (!f) std::cout << "f is false" << std::endl; // sure
   if (!!f) std::cout << "f is true" << std::endl; // also works
   int i = f;              // error
   f << 1;                 // error
   if (f < Foo(false)) ... // error
}

Verdict: Sure, !! is cryptic, but it's also arguably more useful than -->. It's certainly more concise than a static_cast, it can compress numbers down to 0/1 values easily, and it's one way of implementing a safe bool.

Decay (+) operator

In a comment on a stackoverflow question on array decaying, the prolific litb pointed out an unusual use for + operator: it can be used to force types to "decay" (i.e. be promoted) to pointers.

This gruesome operator could be useful if you're dealing with templates and you need a reference to a pointer:

// suppose we're given this method:
template <typename T>
void f(T * const & arg) {
   // ...
}

void g() {}

int main() {
   int arr[] = {3, 1, 4, 1, 5, 9};
   f(arr); // error: no matching function call
   f(+arr); // works!

   f(g); // error: no matching function call
   f(+g); // works!
}

Verdict: I'm torn on this one. It's obscure, but it's arguably nicer than a static_cast to some hard-to-parse type.