Oops! Comma operator is the hardest to understand in the C++ standard!

Discussion in 'C++' started by Lighter, Jun 21, 2007.

1. LighterGuest

In 5.3.3.4 of the standard, the standard provides that "The lvalue-to-
rvalue(4.1), array-to-pointer(4.2),and function-to-pointer(4.3)
standard conversions are not applied to the operand of sizeof."

I think this rule is easy to understand. Because I can find the
contexts of applying the rule as follows.

(1)
int* p = 0;
int b1 = sizeof(*p); // OK, b1 = 4, *p would not be evaluated.

(2)
int array[2];
int b2 = sizeof(array); // OK, b2 = 8, array will not be automatically
converted to a pointer to int

(3)
int b3 = sizeof(strlen); // error, strlen will not be automatically
converted to a function pointer.

So far, everything is understandable.
==========================================

However, In 5.18 of the standard, I find a similar rule on comma
operator: "The lvalue-to-rvalue(4.1), array-to-pointer(4.2),and
function-to-pointer(4.3) standard conversions are not applied to the
left expression."

I tried my best to guess the reason of the latter rule, but failed.
Even, I could not find an example to examine the rule.

(1)
int a, b;
a = b, a++; // compiling is OK. Why? Because a = b will cause an
lvalue-to-rvalue conversion.

(2)

int a[2];
int* b;
b = a, b++; // compiling is OK. Why? Because b = a will cause an array-
to-pointer conversion.

(3)
typedef int (*FP)(int, int);

int Hello(int a, int b)
{
return a + b;
}

FP pf;
pf = Hello, (*pf)(1, 2); // compiling is OK. Why? Because pf = Hello
will cause a function-to-pointer conversion.

I have been thinking the problem for several days, and now I feel lost
and clueless. Who can give me some tips? Any help will be highly
appreciated!

Lighter, Jun 21, 2007

2. Victor BazarovGuest

Lighter wrote:
> In 5.3.3.4 of the standard, the standard provides that "The lvalue-to-
> rvalue(4.1), array-to-pointer(4.2),and function-to-pointer(4.3)
> standard conversions are not applied to the operand of sizeof."
>
> I think this rule is easy to understand. Because I can find the
> contexts of applying the rule as follows.
>
> (1)
> int* p = 0;
> int b1 = sizeof(*p); // OK, b1 = 4, *p would not be evaluated.
>
> (2)
> int array[2];
> int b2 = sizeof(array); // OK, b2 = 8, array will not be automatically
> converted to a pointer to int
>
> (3)
> int b3 = sizeof(strlen); // error, strlen will not be automatically
> converted to a function pointer.
>
> So far, everything is understandable.
> ==========================================
>
> However, In 5.18 of the standard, I find a similar rule on comma
> operator: "The lvalue-to-rvalue(4.1), array-to-pointer(4.2),and
> function-to-pointer(4.3) standard conversions are not applied to the
> left expression."
>
> I tried my best to guess the reason of the latter rule, but failed.
> Even, I could not find an example to examine the rule.
>
> (1)
> int a, b;
> a = b, a++; // compiling is OK. Why? Because a = b will cause an
> lvalue-to-rvalue conversion.

*Inside* the [assignment] expression on the left side of the comma
any conversion does not matter AFA 5.18 is concerned. However, as
the result of the assignment (with return value int&) '(a=b)' is NOT
onverted to r-value. That's what 5.18 is talking about.

>
> (2)
>
> int a[2];
> int* b;
> b = a, b++; // compiling is OK. Why? Because b = a will cause an
> array- to-pointer conversion.

Again, you care safely rewrite this as

b = a;
b, b++;

it has _precisely_ the same effect. In the first (assignment)
expression statement, 'a' is converted to a pointer. However, at
the left side of the comma, 'b' (a reference to a pointer to int)
is not converted into anything.

>
> (3)
> typedef int (*FP)(int, int);
>
> int Hello(int a, int b)
> {
> return a + b;
> }
>
> FP pf;
> pf = Hello, (*pf)(1, 2); // compiling is OK. Why? Because pf = Hello
> will cause a function-to-pointer conversion.

Same thing. You're confusing _where_ the conversion happens. The
operands of the comma operator are evaluated first, and during that
evaluation anything will happen what's supposed to happen. But right
after that evaluation, the result of that evaluation will NOT be

>
> I have been thinking the problem for several days, and now I feel lost
> and clueless. Who can give me some tips? Any help will be highly
> appreciated!

See above.

V
--

Victor Bazarov, Jun 21, 2007

3. Markus SchoderGuest

Re: Oops! Comma operator is the hardest to understand in the C++standard!

On Thu, 21 Jun 2007 09:15:01 -0700, Lighter wrote:
> However, In 5.18 of the standard, I find a similar rule on comma
> operator: "The lvalue-to-rvalue(4.1), array-to-pointer(4.2),and
> function-to-pointer(4.3) standard conversions are not applied to the
> left expression."
>
> I tried my best to guess the reason of the latter rule, but failed.
> Even, I could not find an example to examine the rule.

Since the value of the left expression of the comma operator is discarded
anyway there would be no purpose in converting it. I cannot even see how
one would check whether the conversion happens or not.

> (1)
> int a, b;
> a = b, a++; // compiling is OK. Why? Because a = b will cause an
> lvalue-to-rvalue conversion.

Here the left expression of the comma operator is "a = b" and is not
converted. What is converted is b which is the right hand side of an
assignment.

> [... more examples of assignment rhs and comma left expression
> confusion ...]

--
Markus Schoder

Markus Schoder, Jun 21, 2007
4. LighterGuest

Thank you very much, Victor.

As far as I know, C99 didn't provide the rule. My confusion lies in
the reason for making such a rule. Could you give me an example
illustrating that some conversions are applied on the left expression
(or the left operand)? I could not find such an example. If there were
no such an example. Why did the standard provide such a rule?

Thanks again.

Lighter, Jun 21, 2007
5. Victor BazarovGuest

Lighter wrote:
> Thank you very much, Victor.
>
> As far as I know, C99 didn't provide the rule. My confusion lies in
> the reason for making such a rule. Could you give me an example
> illustrating that some conversions are applied on the left expression
> (or the left operand)? I could not find such an example. If there were
> no such an example. Why did the standard provide such a rule?

I do not know the reason for this rule. Please ask in 'comp.std.c++'
where they discuss and explain the rationales for the ways things are
in the Standard. IOW, we here discuss *how* to do things in C++, and
they there discuss *why*.

V
--

Victor Bazarov, Jun 21, 2007
6. LighterGuest

On Jun 22, 1:03 am, Markus Schoder <> wrote:
> Since the value of the left expression of the comma operator is discarded
> anyway there would be no purpose in converting it. I cannot even see how
> one would check whether the conversion happens or not.

Since one couldn't check whether the conversion happens or not, WHY
did the C++ standard explicitly provide such a rule which doesn't
exist in the C language standard? What confused me is just the
question.

Lighter, Jun 21, 2007
7. John HarrisonGuest

Re: Oops! Comma operator is the hardest to understand in the C++standard!

Lighter wrote:
> On Jun 22, 1:03 am, Markus Schoder <> wrote:
>> Since the value of the left expression of the comma operator is discarded
>> anyway there would be no purpose in converting it. I cannot even see how
>> one would check whether the conversion happens or not.

>
> Since one couldn't check whether the conversion happens or not, WHY
> did the C++ standard explicitly provide such a rule which doesn't
> exist in the C language standard? What confused me is just the
> question.
>
>

LOL, if a tree falls in the forest with no-one around to hear it, does
it make any sound?

Bishop Berkeley

John Harrison, Jun 21, 2007
8. jgGuest

On Jun 21, 10:19 am, Lighter <> wrote:
> On Jun 22, 1:03 am, Markus Schoder <> wrote:

>
> Since one couldn't check whether the conversion happens or not, WHY
> did the C++ standard explicitly provide such a rule which doesn't
> exist in the C language standard? What confused me is just the
> question.

You sure can check if your C++ compiler does the lvalue-to-rvalue
conversion. Try the following code, if it crashes, it does not;
otherwise, it does.

JG
-----------------------
#include <iostream>

int main()
{
int *pi=0;

int x = (*pi, 10);

std::cout << x << std::endl;
return 0;
}

jg, Jun 21, 2007
9. Clark CoxGuest

On 2007-06-21 15:15:18 -0700, jg <> said:

> On Jun 21, 10:19 am, Lighter <> wrote:
>> On Jun 22, 1:03 am, Markus Schoder <> wrote:

>
>>
>> Since one couldn't check whether the conversion happens or not, WHY
>> did the C++ standard explicitly provide such a rule which doesn't
>> exist in the C language standard? What confused me is just the
>> question.

>
> You sure can check if your C++ compiler does the lvalue-to-rvalue
> conversion. Try the following code, if it crashes, it does not;
> otherwise, it does.

Wrong. You can't conclude anything from the given code, as it invokes
undefined behavior.

>
> JG
> -----------------------
> #include <iostream>
>
> int main()
> {
> int *pi=0;
>
> int x = (*pi,

At this point all bets are off, the program could crash, it could
assign 10 to x, it could let loose the proverbial nasal demons.

> 10);
>
> std::cout << x << std::endl;
> return 0;
> }

--
Clark S. Cox III

Clark Cox, Jun 21, 2007
10. LighterGuest

On Jun 22, 6:15 am, jg <> wrote:
> On Jun 21, 10:19 am, Lighter <> wrote:
>
> > On Jun 22, 1:03 am, Markus Schoder <> wrote:

>
> > Since one couldn't check whether the conversion happens or not, WHY
> > did the C++ standard explicitly provide such a rule which doesn't
> > exist in the C language standard? What confused me is just the
> > question.

>
> You sure can check if your C++ compiler does the lvalue-to-rvalue
> conversion. Try the following code, if it crashes, it does not;
> otherwise, it does.
>
> JG
> -----------------------
> #include <iostream>
>
> int main()
> {
> int *pi=0;
>
> int x = (*pi, 10);
>
> std::cout << x << std::endl;
> return 0;
>
>
>
> }- Hide quoted text -
>
> - Show quoted text -

I tried with VS 2005. It didn't crash. That means the compiler doesn't
perform lvalue-to-rvalue conversion.

Lighter, Jun 21, 2007
11. Victor BazarovGuest

Lighter wrote:
> On Jun 22, 6:15 am, jg <> wrote:
>> On Jun 21, 10:19 am, Lighter <> wrote:
>>
>>> On Jun 22, 1:03 am, Markus Schoder <> wrote:

>>
>>> Since one couldn't check whether the conversion happens or not, WHY
>>> did the C++ standard explicitly provide such a rule which doesn't
>>> exist in the C language standard? What confused me is just the
>>> question.

>>
>> You sure can check if your C++ compiler does the lvalue-to-rvalue
>> conversion. Try the following code, if it crashes, it does not;
>> otherwise, it does.
>>
>> JG
>> -----------------------
>> #include <iostream>
>>
>> int main()
>> {
>> int *pi=0;
>>
>> int x = (*pi, 10);
>>
>> std::cout << x << std::endl;
>> return 0;
>>
>>
>>
>> }- Hide quoted text -
>>
>> - Show quoted text -

>
> I tried with VS 2005. It didn't crash. That means the compiler doesn't
> perform lvalue-to-rvalue conversion.

No, it doesn't mean squat. Undefined behaviour is just that, undefined.
If the compiler [by not following the Standard] actually performs the
conversion, there is no way to tell by observing the undefined behaviour
of the program above.

V
--

Victor Bazarov, Jun 22, 2007
12. James KanzeGuest

On Jun 21, 6:15 pm, Lighter <> wrote:

First, I have to say that I like the subject line. The comma
operator is a major misfeature in C++, and most of the coding
guidelines I've seen forbid its use. (A large part of the
problem, of course, is that the comma isn't always an operator.)

> In 5.3.3.4 of the standard, the standard provides that "The lvalue-to-
> rvalue(4.1), array-to-pointer(4.2),and function-to-pointer(4.3)
> standard conversions are not applied to the operand of sizeof."

> I think this rule is easy to understand. Because I can find the
> contexts of applying the rule as follows.

[...]
> However, In 5.18 of the standard, I find a similar rule on comma
> operator: "The lvalue-to-rvalue(4.1), array-to-pointer(4.2),and
> function-to-pointer(4.3) standard conversions are not applied to the
> left expression."

The wording could, perhaps, be better. In this case, the "left
expression" refered to is the "expression" which appears to the
left of the comma operator in the production "expression ,
assignment-operator".

> I tried my best to guess the reason of the latter rule, but failed.
> Even, I could not find an example to examine the rule.

> (1)
> int a, b;
> a = b, a++; // compiling is OK. Why? Because a = b will cause an
> lvalue-to-rvalue conversion.

The expression "a = b" will not be subject to lvalue to rvalue
conversion. The expression "a = b" is not used (although it
must be evaluated for side effects). "b", of course, will be
subjected to an lvalue to rvalue conversion, but "b", by itself,
is NOT the expression to the left of the comma operator. The
expression (in the grammar production) to the left of the comma
operator is "a = b". That expression is an lvalue, and it will
not be subject to an lvalue to rvalue conversion.

> (2)

> int a[2];
> int* b;
> b = a, b++; // compiling is OK. Why? Because b = a will cause an array-
> to-pointer conversion.

As above. The type of the expression "b = a" is int*; there's
no array to be converted in the expression to the left of the
comma operator.

> (3)
> typedef int (*FP)(int, int);

> int Hello(int a, int b)
> {
> return a + b;
> }

> FP pf;
> pf = Hello, (*pf)(1, 2); // compiling is OK. Why? Because pf = Hello
> will cause a function-to-pointer conversion.

As above. The expression to the left of the comma is "pf =
Hello". It has type pointer to function, not type function.

> I have been thinking the problem for several days, and now I
> feel lost and clueless. Who can give me some tips? Any help
> will be highly appreciated!

The word "expression" in "The lvalue-to-rvalue (4.1),
array-to-pointer (4.2), and function-to-pointer (4.3) standard
conversions are not applied to the left expression" refers to
the "expression" to the left of the comma in the grammar
production, not to any expression which happens to appear to the
left of the comma.

Globally, most of the time, it doesn't matter. There aren't
many ways you could tell. I can only think of three:

int a ;
a, f() ; // Not undefined, despite a not being
// initialized (thanks Victor).

extern int a[] ;
a, f() ; // No definition for a required, because
// no array to pointer conversion, and a
// still not considered used. (Not 100%
// sure on this one.)

volatile int a ;
a, f() ; // No "access" to a.

In practice, however, in almost all programs, it just doesn't
matter.

--
James Kanze (GABI Software, from CAI) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

James Kanze, Jun 22, 2007
13. James KanzeGuest

On Jun 21, 7:08 pm, Lighter <> wrote:
> Thank you very much, Victor.

> As far as I know, C99 didn't provide the rule.

In C99, it says "The left operand of a comma operator is
evaluated as a void expression". This more or less implies that
the lvalue to rvalue conversion cannot occur, because there are
no rvalues of type void. For the other conversions, C
explicitly says that they do occur in this case.

In practice, I don't think it makes a difference. I've
rechecked the standard, and my supposition that without the
array-to-pointer conversion, the object wasn't used, is wrong;
an object is used as soon as it appears in a potentially
evaluated expression (and only the operands of sizeof, and some
cases with typeid, and some cases of integral constant
expressions are not potentially evaluated expressions). And I
can't think of any other way of determining whether the
conversion occurs or not here.

--
James Kanze (GABI Software, from CAI) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

James Kanze, Jun 22, 2007
14. James KanzeGuest

On Jun 22, 2:00 am, "Victor Bazarov" <> wrote:
> Lighter wrote:
> >> -----------------------
> >> #include <iostream>

>
> >> int main()
> >> {
> >> int *pi=0;

>
> >> int x = (*pi, 10);

>
> >> std::cout << x << std::endl;
> >> return 0;

> >> }

> > I tried with VS 2005. It didn't crash. That means the compiler doesn't
> > perform lvalue-to-rvalue conversion.

> No, it doesn't mean squat. Undefined behaviour is just that, undefined.
> If the compiler [by not following the Standard] actually performs the
> conversion, there is no way to tell by observing the undefined behaviour
> of the program above.

And traditionally, dereferencing a null pointer is undefined
behavior, regardless of whether there is an lvalue to rvalue
conversion afterwards, or not. Something like &*p is undefined
behavior in p is a null pointer, according to the C++ standard.

About the only sure way I can think of is something like:

int volatile* p = someMemoryMappedIOAddress ;
*p, 10 ;

If there is an lvalue to rvalue conversion, the compiler will
(should) generate code to access the memory mapped IO, which is
observable behavior (in theory, anyway). Without the lvalue to
rvalue conversion, no access.

--
James Kanze (GABI Software, from CAI) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

James Kanze, Jun 22, 2007
15. LighterGuest

Lighter, Jun 22, 2007
16. Guest

> (1)
> int* p = 0;
> int b1 = sizeof(*p); // OK, b1 = 4, *p would not be evaluated.
>

Using type int to illustrate this, I think is not a good example.
For in 64-bit system, b1 will be 8 .

double* p = 0;
int b1 = sizeof(*p); // b1 = 8, I did this in VS2005

, Jun 24, 2007