Confused about the comma operator

D

Derek

I came upon the idea of writting a logging class that
uses a Python-ish syntax that's easy on the eyes (IMO):

int x = 1;
double y = 2.5;
std::string z = "result";

debug = "Results:", x, y, z;

The above example outputs:

Results: 1 2.5 result

The implementation uses a template comma operator and a
template operator= to kick things off. Here is a very
minimal implementation:

struct Value
{
template<typename T>
Value(const T& x)
{ std::cout << x << ' '; }

template<typename U>
Value operator,(const U& y)
{ return Value(y); }
};

struct Log{
template<typename T>
Value operator=(const T& x)
{ return Value(x); }
} debug;

It works, but now that I think about it, I'm not sure
WHY it works. Does it work because the operator= has
higher precedence than the comma operator, so the
compiler sees it like this:

(debug = "Results:"), x, y, z;

Of course operator= returns a Value object which
displays "Result" and (thanks to the template comma
operator) keeps the chain going in the normal
left-to-right evaluation order?

If this is the case, then I find it counter-intuitive
because the type and value of an expression in the form
(e1,e2) is e2. I find this behavior a bit surprising
and inconsistent:

void print(int i)
{ std::cout << i << '\n'; };

int w;
w = 1, 2; // w = 1
print(w); // prints 1
print((1,2)); // prints 2
 
L

Leor Zolman

It works, but now that I think about it, I'm not sure
WHY it works. Does it work because the operator= has
higher precedence than the comma operator, so the
compiler sees it like this:

(debug = "Results:"), x, y, z;
Right.


Of course operator= returns a Value object which
displays "Result" and (thanks to the template comma
operator) keeps the chain going in the normal
left-to-right evaluation order?
Sure.


If this is the case, then I find it counter-intuitive
because the type and value of an expression in the form
(e1,e2) is e2. I find this behavior a bit surprising
and inconsistent:

void print(int i)
{ std::cout << i << '\n'; };

int w;
w = 1, 2; // w = 1
print(w); // prints 1
print((1,2)); // prints 2

The evaluation order of the comma operator is fixed as left-to-right; this
is more than a simple associativity rule, as in the case of, say, the
additive operators where
a + b
give the compiler the freedom to evaluate either a or b first (or both
simultaneously). Since the order of evaluation of the comma operator /is/
fixed, it makes the most sense to have the value of the result be the value
of the last operand evaluated; otherwise the compiler might have to do
extra work to "push" the value of the left-most operand before evaluating
the rest, and then "pop" that value afterwards.
-leor
 
D

Derek

Leor said:
Since the order of evaluation of the comma operator
/is/ fixed, it makes the most sense to have the
value of the result be the value of the last operand
evaluated; otherwise the compiler might have to do
extra work to "push" the value of the left-most
operand before evaluating the rest, and then "pop"
that value afterwards

That makes sense and I find the left-to-right behavior
of the comma operator perfectly sensible. I just
think that the fact that it has the lowest precedence
of all operators can lead to unexpected behavior. To
repeat my simple example:

int x;
x = 1,2; // x is 1

I would expect x to be 2 because of the left-to-right
evaluation of the comma operator, but here op= takes
precedence.
 
L

Leor Zolman

That makes sense and I find the left-to-right behavior
of the comma operator perfectly sensible. I just
think that the fact that it has the lowest precedence
of all operators can lead to unexpected behavior. To
repeat my simple example:

int x;
x = 1,2; // x is 1

I would expect x to be 2 because of the left-to-right
evaluation of the comma operator, but here op= takes
precedence.

One thing Java got right, IMHO, was to limit the use of the comma operator
to the "init" and "incr" portions of for loops. This would have eliminated
an awful lot of grief wrt the comma operator's precedence in C and C++. I
learned early on to always think of it as "the lowly comma operator," as a
mental cue to not lose track of its precedence whenever I encountered its
use in expressions other than as part of the aforementioned 'for' loop
constructs. It took the emergence of Stupid Template Tricks such as yours
(and I use the term affectionately, /not/ disparagingly) and Thorsten
Ottosen's initialization library:

http://www.codeproject.com/vcpp/stl/PGIL.asp?print=true

to finally give the comma operator some good reason for existence other
than "for" loop management.
-leor
 
O

Old Wolf

Leor Zolman said:
One thing Java got right, IMHO, was to limit the use of the comma operator
to the "init" and "incr" portions of for loops. This would have eliminated
an awful lot of grief wrt the comma operator's precedence in C and C++.
[ ... ]
to finally give the comma operator some good reason for existence other
than "for" loop management.

I have found some other occasional uses for it:

1. In preprocessor macros, to enable them to act like functions with a
return value (and to avoid the do..while(0) construct)

2. To return an error code and also perform error logging, concisely:
if (FAILED(foo()))
return do_log(whatever), ERR_FOO_FAILED;

Admittedly these are C-based uses that can probably be superseded
by more natural C++ constructs.

Also, in the same vein as 2, sometimes if my actions to perform in a
block after an "if" or "else" are concise, or conceptually one action
that requires 2 statements, I will write "a, b;" instead of going to
4 lines by using curly braces and a semicolon.
 
B

Bill Seurer

Old said:
I have found some other occasional uses for it:

1. In preprocessor macros, to enable them to act like functions with a
return value (and to avoid the do..while(0) construct)

Using the preprocessor for this kind of thing is almost always VERY BAD.
2. To return an error code and also perform error logging, concisely:
if (FAILED(foo()))
return do_log(whatever), ERR_FOO_FAILED;

This is silly. No, on second thought it is stupid.

if (FAILED(foo)) {
do_log(whatever);
return ERR_FOO_FAILED;
}

Also, see below.
Admittedly these are C-based uses that can probably be superseded
by more natural C++ constructs.

Also, in the same vein as 2, sometimes if my actions to perform in a
block after an "if" or "else" are concise, or conceptually one action
that requires 2 statements, I will write "a, b;" instead of going to
4 lines by using curly braces and a semicolon.

What is with this whole "It's more concise (i.e., better) if I squeeze
it all on one line!" thing that so many programmers seem to have a
religious belief in? If you MUST do this kind of junk:

if (FAILED(foo)) { do_log(whatever); return ERR_FOO_FAILED; }

There! It's better than your two line thing 'cause it's only one line
right?
 
O

Old Wolf

Bill Seurer said:
Using the preprocessor for this kind of thing is almost always VERY BAD.

Why? Here's an example (without return value, but you could concoct one
if you like):
struct buffer
{
char text[2000];
char *next;
};
#define BUFFER_COPY(dst, src) \
( memcpy((dest)->text, (src)->text, sizeof((dest)->text)), \
(dest)->next = (dest)->text + ((src)->next - (src)->text) )

Admittedly this isn't something you do all the time.
(Note - compiler doesn't support inlining of functions; code
occurs in a header file so a non-inline function would cause
too much executable size bloat (limited space available),
and it would be tedious to copy out the code wherever it was required)

This is silly. No, on second thought it is stupid.

if (FAILED(foo)) {
do_log(whatever);
return ERR_FOO_FAILED;
}

What is with this whole "It's more concise (i.e., better) if I squeeze
it all on one line!" thing that so many programmers seem to have a
religious belief in?

I develop on 80x25 terminal screens, and it is easier to comprehend
a function if there is more of its control structures on the screen.
Error handling code is annoying but necessary, so I like to make it
'waste' as little screen space as possible.
If you MUST do this kind of junk:

if (FAILED(foo)) { do_log(whatever); return ERR_FOO_FAILED; }

There! It's better than your two line thing 'cause it's only one line
right?

Also acceptable, but it has more characters than my version, and
I prefer to keep the "if" on a separate line to any ensuing
characters, for readability.

I'm not saying you have to adopt my coding style -- just noting that
it is possible to use the comma operator regularly.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,226
Members
46,815
Latest member
treekmostly22

Latest Threads

Top