The forgotten comma operator

Most of us are familiar with comma as separators in C/C++.

int add(int a, int b) // parameter list for(int i=0, j=list.size(); i < j; i++) // variable declaration list MAX(2, 3) // macro argument list

What’s lesser known is that the comma is also an operator that has the lowest level of precedence amongst all operators. The comma operator evaluates its operands from left to right and returns the value of the last operand.

Let’s take an example.

int result = (2, 3); // result is 3

The evaluation proceeds this way:

(2, 3) evaluates to 3 result is then assigned 3

When we wrap the RHS with parenthesis, we give it the highest order of precedence. In fact, if we omitted the parenthesis, the code will simply fail to compile.

int result = 2, 3; // error: expected unqualified-id before numeric constant

Practical uses

The comma operator allows you to specify more than one expression where the language expects only one. Unfortunately, since the usage of comma as an operator is so poorly understood, I have seen succinct code being labelled as confusing because of the comma.

The comma operator is handy when you want to increment more than a single variable in a loop. Let’s say you want to split a list of numbers into roughly equal sized chunks, you can write something like this:

const size_t LIST_SIZE = numbers.size(); const size_t CHUNK_SIZE = (LIST_SIZE / NUMBER_OF_CHUNKS) + 1; // `i` and `chunkIndex` are both incremented in the outer loop for (int i = 0, chunkIndex = 0; i < numbers.size(); i += CHUNK_SIZE, chunkIndex++) { for(int k = 0; k < CHUNK_SIZE && i+k < LIST_SIZE; k++) { // type of `chunks` is vector<vector<>> chunks[chunkIndex].push_back(numbers[i+k]); } }

Notice how i and chunkIndex are both incremented in the outer loop?

You can also use the comma to pack multiple statements into a ternary operator. Usually, if else is the right way to go if you want to conditionally execute multiple statements, but sometimes the ternary operator along with the comma provides a succinct alternative. Here’s a synthetic example:

(i > 0) ? (i--, doSomething()) : doSomethingElse();

Overloading Comma

C++ allows you to overload the comma operator. Boost Spirit uses this to a great effect for implementing list initializers for symbol tables.

symbols<> logicalOperators = "AND", "OR", "NOT";

Spirit achieves this by overloading the equal and comma operators. So, the above statement is identical to:

logicalOperators.operator =("AND").operator ,("OR").operator ,("NOT");

The first operator =() method returns an object on which the other operator ,() s are invoked. Interestingly, remember how I mentioned that the the parenthesis is mandatory in the very first example? It turns out that in this particular case, wrapping the list of operators with a parenthesis will not work!

symbols<> logicalOperators = ("AND", "OR", "NOT"); // will fail

With a parenthesis, the compiler just sees the comma operator between two char* , which cannot be overloaded.

Comma in JavaScript

JavaScript has inherited the comma operator from C, and is often used by minifiers. For example,

if(x) { foo(); return bar(); } else { return 1; }

will be minfied to

return x?(foo(),bar()):1;

In ECMAScript 6, the comma is also used for destructuring assignment. You can use Firefox’s web console to tinker with this.

var [x, y] = [1, 2]; x === 1; // true y === 2; // true

And, that brings us to the end of a short essay on the forgotten operator!

This post was written by Kishore Nallan