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.