C++ Comma Operator series:
- 1. Introduction to our filthy friend
- 2. Nice usages
Comma operator has been with us for a long time. First seen in C spec and improved with custom overloads in C++, it quickly became one of those hidden things you shouldn’t use.
Most C/C++ books avoid speaking about
goto the same way they do about comma operator. This is not fair, as both of them can be used properly on certain cases. Let’s speak about that.
But first, what does it do?
Comma operator is associative, it returns the result of the last expression after evaluating all of them (from left to right, one by one as any other binary operation) and discards the left-side result just before returning (if it has class type, it won’t be destroyed until the end of the containing full expression).
cout << (1, 2, 3); // prints `3`
Back in C times, returning something different than rvalue from the operator wasn’t allowed. This changed in C++, so it returns exactly the last element with it’s type being lvalue, rvalue or whatever. This feature allows writing things like:
cout << &(a, b); // address of `b`
Another nice point of the comma is that it guarantees sequenced-before evaluation, in other words: ensures that all the sub-expressions will be evaluated in order (§8.19.2). This is a really nice feature, as the compiler is usually allowed to evaluate operands in an arbitrary order.
int i = 0; cout << ++i + i++; // undefined behavior cout << (i=3, i+=2, i+3); // 8 (perfectly defined behavior)
operator,()cannot guarantee sequencing!
Short point about precedence
It has the lowest precedence in the language, which makes it quite unstable when mixed with other operators.
int y; y = 1, 2, 3; // y = 1 // because assignment operator has higher precedence, // previous line is equivalent to // (y = 1), 2, 3;
Did you notice the parenthesis at the very first example in the post? It’s needed because bitwise shift operator (used by std::ostream) also has a higher precedence than comma. Using parenthesis is crucial here as it directly affects the result of the expression:
cout << 1, 2, 3; // prints `1` cout << (1, 2, 3); // prints `3` cout << 1, 2, 3 << endl; // compilation error // because is equivalent to // (cout << 1), 2, (3 << endl) // ^~~~~~~ invalid operands // step by step evaluation: // x = (1, 2, 3); // ((1,2),3) // (2,3) // (3)
Always use parenthesis to avoid nasty bugs.
Thanks for reading!