views:

705

answers:

5

I found this piece of code on Wikipedia.

#include <stdio.h>

int main(void)
{
  int c;

  while (c = getchar(), c != EOF && c != 'x')
  {
    switch (c)
      {
      case '\n':
      case '\r':
        printf ("Newline\n");
        break;
      default:
        printf ("%c",c);
      }
  }
  return 0;
}

I'm curious about expression used as condition for while loop:

while (c = getchar(), c != EOF && c != 'x')

It's quite obvious what it does, but I've never seen this construction before. Is this specific to while loop? If not, how does parser/compiler determine which side of comma-separated expression returns boolean value for while loop?

+10  A: 

The comma operator is a binary operator that evaluates its first operand and discards the result, it then evaluates the second operand and returns this value.

It is also a "sequence point", which means that all side effects will be calculated before the next part of the code is executed.

Paul Dixon
Just to add, because it may not be obvious - the effect of the "=" operator updating the value of the variable c is considered a "side effect" in this context.
caf
Correct - because the comma operator has the lowest precedence
Paul Dixon
+1  A: 

The comma is an operator. It returns the value of the right hand expression by default. The order of evaluation is guaranteed to be left first and then right.

UPDATE (reply to Pax's comment):

Just like most operators, it can be overloaded for user defined types:

#include <iostream>
#include <string>
using namespace std;

enum EntryType { Home, Cell, Address };

class AddressBookEntryReference {
public:
    AddressBookEntryReference(const string& name, const EntryType &entry)
     : Name(name), Entry(entry) { }
    string Name;
    EntryType Entry;
};

AddressBookEntryReference operator,(const string& name, const EntryType &type) {
    return AddressBookEntryReference(name, type);
}

class AddressBook {
    string test;
public:
    string& operator[](const AddressBookEntryReference item) {
     // return something based on item.Name and item.Entry.

     // just to test:
     test = item.Name;
     return test;
    }
};

int main() {
    // demo:
    AddressBook book;
    cout << book["Name", Cell]  // cool syntax! 
         << endl;
}
Mehrdad Afshari
I'm curious re the phrase "by default" - you're not suggesting this behavior can be configured, are you? I think the standard makes it pretty clear this is the way it is, no deviation allowed.
paxdiablo
My original question was relating to C language - you have stayed deep into C++ waters. In any case, very interesting use of overloading.
Josip
Josip: Oh, I didn't notice the C tag. My original answer is applicable to both but my reply to Pax's comment is definitely C++ only.
Mehrdad Afshari
Apologies, @Mehrdad, I didn't think of the C++ angle.
paxdiablo
+1  A: 

In many languages the comma is an operator that always results in the value of the second operand. The operands are evaluated sequentially from left to right.

Pseudo-code:

a = 10
print a = 7 + 8, a * 2

Note: print is considered a statement that does not take arguments, so what comes after is considered the single expression a = 7 + 8, a * 2.

Executed like this:

  • First line
    • put 10 in a
  • Second line
    • evaluate 7 + 8 (15)
    • put evaluated value (15) in a
    • evaluate a * 2 (30)
    • evaluate , operator with operands 15 and 30:
      • always value of second operand (30)
    • print evaluated value (30)
Blixt
+6  A: 

The comma operator is a weird beastie until you get to understand it, and it's not specific to while.

The expression:

exp1, exp2

evaluates exp1 then evaluates exp2 and returns exp2.

You see it frequently, though you may not realize it:

for (i = j = 0; i < 100; i++, j += 2)

You're not actually using the return value from "i++, j += 2" but it's there nonetheless. The comma operator evaluates both bits to modify both i and j.

You can pretty well use it anywhere a normal expression can be used (that comma inside your function calls is not a comma operator, for example) and it's very useful in writing compact source code, if that's what you like. In that way, it's part of the family that allows things like:

while ((c= getchar()) != EOF) {...}
i = j = k = 0;

and so on.

For your specific example:

while (c = getchar(), c != EOF && c != 'x')

the following occurs:

  • c = getchar() is executed fully (the comma operator is a sequence point).
  • c != EOF && c != 'x' is executed.
  • the comma operator throws away the first value (c) and "returns" the second.
  • the while uses that return value to control the loop.
paxdiablo
If I'd want to chain several comma operators in one expression, would it be safe to do so without parenthesis?
Josip
Yes, the comma operator has the lowest precedence of all.
caf
@Josip, I'd be careful about it, the comma operator has a very low precedence which should make it okay, but you may find yourself with unexpected results. Although I think you *can* use it anywhere an expression is allowed, that doesn't necessarily mean you *should*.
paxdiablo
+2  A: 

To expand a bit on the other answers, in this code:

EXPRESSION_1 , EXPRESSION_2

EXPRESSION_1 is first evaluated, then there is a sequence point, then EXPRESSION_2 is evaluated, and the value of the whole thing is the value of EXPRESSION_2.

The order of operation guarantee and the sequence point are both important to the code you quoted. Together, they mean that we can be certain that the getchar() function gets called and the value of variable c is fully updated before the value of c is tested.

caf