PDA

View Full Version : Any thoughts on the comma operator (C/C++/Obj-C?)




ArtOfWarfare
Feb 21, 2013, 08:13 PM
I recently decided to insert a few comma operators into the output lines of my C++ code and I feel like they've cleaned up my code a lot.

For example, I had this code:

cout << "Can't place " << v << " at " << x << ", " << y << endl << "Add: ";
printBitsForInt(mask);
cout << "Old: ";
printBitsForInt(conflicts[v-1]);
cout << endl;

Just an FYI, printBitsForInt() takes an int and prints out the bits from it. I couldn't find any way to overload the insertion operator between a stream and an int (because it's already defined by C++.) I couldn't find any stream parameters for cout I could set to make it print ints as binary, either. I tried using a bitset container, but it didn't allow its contents to be printed out with the grouping I wanted (groups of nine bits with spaces between them.)

And by introducing a few sequence points I feel like I was able to clean it up quite a bit, improve legibility, and reduce the amount of space it takes up.

cout << "Can't place " << v << " at " << x << ", " << y << endl
<< "Add: ", printBitsForInt(mask), cout << endl;
<< "Old: ", printBitsForInt(conflicts[v-1]), cout << endl;

I'm a little confused why I so rarely see anyone using them.

I'm wondering if I could use the same basic idea in the future with some C or Obj-C code.

Also, is there any real difference between using the comma operator and inserting a semicolon? I feel like the compiler probably generates the same code for both, wouldn't it?

Are there any other good uses for the comma operator?



gnasher729
Feb 21, 2013, 08:26 PM
I recently decided to insert a few comma operators into the output lines of my C++ code and I feel like they've cleaned up my code a lot.

I felt like that for a very short period after learning C. Then I felt it made the code less readable.

Also, is there any real difference between using the comma operator and inserting a semicolon? I feel like the compiler probably generates the same code for both, wouldn't it?
There's no reason why the code would be different.

Are there any other good uses for the comma operator?

Very, very rare. This would be an example:

while (tmp = x*x + y*y, tmp > 10 && tmp < 20) { ... }

instead of

for (;;) {
tmp = x*x + y*y;
if (tmp <= 10 || tmp >= 20) break;
...
}

lee1210
Feb 21, 2013, 10:52 PM
I've never felt the need to use it, I've rarely seen it at all, really. Executing multiple statements in the incrementor of a for, etc. is a rare but useful case. I had to fix one very pernicious bug caused by a comma operator, which put it into my bad graces.

As for the difference between ; and , is that ; ends a statement and , joins statements and evaluates to the right operand.
x = haveFun(), eatPie(), takeNap();
Will result in x being set to the result of takeNap().

I'm not big on C++, but %b is the format specifier for printing the binary representation of an int. Why not return a std::string from your helper, then just keep using <<. Having the function print after formatting that way limits the utility.

-Lee

Edit: figured there was a better way. Try:
cout << setbase(2) << myInt << endl;

chown33
Feb 21, 2013, 11:47 PM
Very, very rare. This would be an example:

while (tmp = x*x + y*y, tmp > 10 && tmp < 20) { ... }

instead of

for (;;) {
tmp = x*x + y*y;
if (tmp <= 10 || tmp >= 20) break;
...
}

I used to write loops like the former, or try to. Then I spent way too much time trying to figure them out weeks later, or trying to debug them at the time. Now I only do it like the latter (though I usually put the break on its own indented line).


My thoughts regarding the comma operator:
1. It can make it difficult to set a breakpoint at a statement, if the statement is a bunch of comma-separated expressions. Better to make each one a separate statement.

2. There were times when an aggressive optimizer wouldn't rearrange the parts of a comma'ed statement, but it would rearrange statements with semicolons. I don't know how true that is any more, since optimizers are a lot better these days. Still, it wouldn't surprise me if commas aren't entirely interchangeable with semicolons when it comes to code rearrangement.

3. If you want to add something between the comma'ed expressions, it gets ugly very quickly. Even when the expressions may seem to be inseparable in the design, things can evolve and change over time.

4. One place where I still use commas is in the initializer (1st) or the iterator (3rd) clauses of a for loop, e.g.:
for ( a = x, b = y; someCondition; ++a, --b )
{ something using both a and b; }
The fact that a continue in a for loop will go to the iterator part means that if there's more than one iterative calculation, you can guarantee that both are always done. However, there are also times when you DON'T want both things done, and for that I'd probably write it as a for (;;) so every conditional pathway was easy to follow.


EDIT
As for the difference between ; and , is that ; ends a statement and , joins statements and evaluates to the right operand.
x = haveFun(), eatPie(), takeNap();
Will result in x being set to the result of takeNap().

I think the underlined part is incorrect. Assignment binds tighter than comma, so the assignment is done first, then eatPie() is called, then takeNap(). Effectively:
(x = haveFun()), eatPie(), takeNap();


If this precedence weren't so, then this expression wouldn't work:
a = x, b = y

Since functions that return values can have the value ignored with little or no compiler complaint, the fact that takeNap() returns a value that's ignored would not cause the compiler to warn about it. Unexpected results ensue.

I don't have the time right now to write up a test case, but doing so would prove enlightening on several levels. One level is the fundamental operation of comma: its precedence, associativity, debuggability, etc. Another level is the potential confusion or errors when using it, even for skilled programmers with years of experience. I include myself in that bunch of confused error-makers, because I could be wrong. It's exactly the kind of thing I'd only trust after writing test code for, and frankly, if I have to write a test to figure out how it works, is it really worth using in real-world code? I could name all my variables as nothing but different numbers of _'s, too, but why would I do that except to sow confusion?

lee1210
Feb 22, 2013, 01:38 AM
Oops, there you go.
x = (haveFun(),eatPie(),takeNap());
Works the way I was thinking. Screwed up the precedence, but was right about the second operand being the result.

One more data point for myself generally avoiding the , operator. Clauses in a for loop are the rare exception.

-Lee

gnasher729
Feb 22, 2013, 02:27 AM
1. It can make it difficult to set a breakpoint at a statement, if the statement is a bunch of comma-separated expressions. Better to make each one a separate statement.

That's a very important argument. Making your code easy to debug is very important. I tend to write code with "setting breakpoints" in mind. For example: The first one doesn't let you easily examine the result of calling f (x), the second does.

return f (x);

int result = f (x);
return result;

whooleytoo
Feb 22, 2013, 05:42 AM
Yikes. I'm ashamed to admit after years of study and more years of development work on C-esque languages, I'd never even heard of the comma operator.

I'm kind of glad I didn't though. If there's one thing I've learned through experience it's the importance of code maintainability, and having easily readable and debuggable code is far, far more important than keeping code terse.

Even reading through the Wikipedia, the simple examples gave me a shiver:
i = (a,b); // b
i = a,b; // a

I can see how one of those lines, buried in hundreds of lines of code, could introduce an easily missed bug.

960design
Feb 22, 2013, 07:42 AM
Occasionally, I'll come up with an amazing line of code that is pure poetry. I take a sip of tea, then break the line into about 30 separate lines of easily readable code and let the compiler do it's job of making the executable efficient.

Coming back to fix something 4 months and 6 projects later on poetry is a serious PITA.

gnasher729
Feb 22, 2013, 08:12 AM
Even reading through the Wikipedia, the simple examples gave me a shiver:
i = (a,b); // b
i = a,b; // a

I can see how one of those lines, buried in hundreds of lines of code, could introduce an easily missed bug.

That's why you turn on all compiler warnings that you can. i = a, b; doesn't even compile on my Mac (it produces a warning because the value b is ignored, and I turn all warnings into errors).

notjustjay
Feb 22, 2013, 01:33 PM
I had to fix one very pernicious bug caused by a comma operator

This is why I tend not to go for "clever" code. I work on a codebase that's huge (500K+ lines), historical (some of this code was written in the 80's) and maintained by dozens of people. Code I wrote today might be maintained by someone else 6 months from now when I transfer to a different project. Likewise, I'll frequently inherit code written 4 years ago by other programmers who no longer work with the company.

To me, good code is cleanly formatted, straightforward, and easy to follow (with comments where helpful). If I have to stop and say "wait wait wait, what is this doing?" then that slows things down and potentially allows bugs to go unnoticed.

ArtOfWarfare
Feb 22, 2013, 01:54 PM
Edit: figured there was a better way. Try:
cout << setbase(2) << myInt << endl;

Huh. Can I have it put a space after every 9th bit/digit?

lee1210
Feb 22, 2013, 03:58 PM
Huh. Can I have it put a space after every 9th bit/digit?

You can probably write your own modifier, but otherwise I'd just write a formatter that returns what you want to print. I'll happily admit that I don't know the right way to do this safely as it pertains to memory in C++, but surely this is a solved problem.

-Lee

knightlie
Feb 26, 2013, 04:46 AM
Even reading through the Wikipedia, the simple examples gave me a shiver:
i = (a,b); // b
i = a,b; // a


Wow. :eek: