left handed use of conditional

N

Nicholas M. Makin

I was just thinking that I understood the conditional operator when I coded
the following expecting it to fail:

int a= 10, b= 20, c= 0;
((a < b) ? a : b) = c; // a=0
a=20; b= 10;
((a < b) ? a : b) = c; //b=0

Now since the expresion 5 = c is not valid I did not expect the above to
work. But it does work. Why? I thought that the ?: operator would evaluate
to the value of "a" or "b" not to some kind of pointer to the value. This
really leaves me wondering how the operator works.
 
C

Carl Daniel [VC++ MVP]

Nicholas said:
I was just thinking that I understood the conditional operator when I
coded the following expecting it to fail:

int a= 10, b= 20, c= 0;
((a < b) ? a : b) = c; // a=0
a=20; b= 10;
((a < b) ? a : b) = c; //b=0

Now since the expresion 5 = c is not valid I did not expect the above
to work. But it does work. Why? I thought that the ?: operator would
evaluate to the value of "a" or "b" not to some kind of pointer to
the value. This really leaves me wondering how the operator works.

This results from the concepts of "lvalues" and "rvalues". (Basically, an
"lvalue" can be assigned-to, while an "rvalue" cannot).

The conditional operator returns one of two expressions. If both
expressions are l-values, then the result of the conditional is also an
l-value, as you've discovered. If either expression was not an l-value,
then the one that was an l-value would be subject to an "lvalue to rvalue
conversion", and the result of the expression would be an r-value and not
assignable.

int a, b;
((a < b ) ? a : 5) = 1; // won't compile

You'll find the conditional operator works in other possibly surprising
cases as well:

void f(int) { ... }
void g(int) { ... }

int a,b;

((a < b) ? f : g)(10); // calls either f or g with parameter 10


int p[100], q[100];

((a < b) ? p : q)[10] = 0; // zero out 10th element of *p or *q

-cd
 
D

David Lowndes

I was just thinking that I understood the conditional operator when I coded
the following expecting it to fail:

int a= 10, b= 20, c= 0;
((a < b) ? a : b) = c; // a=0
a=20; b= 10;
((a < b) ? a : b) = c; //b=0

Now since the expresion 5 = c is not valid I did not expect the above to
work. But it does work. Why? I thought that the ?: operator would evaluate
to the value of "a" or "b" not to some kind of pointer to the value. This
really leaves me wondering how the operator works.

I'm no language expert, and the usage does seem odd, but even the
Comeau compiler accepts it, so I'd guess it's perfectly valid and does
something like this:

if ( a < b )
a = c;
else
b = c;

Dave
 
N

Nicholas M. Makin

Sorry I did not mean to send you an email...
The conditional operator returns one of two expressions. If both
expressions are l-values, then the result of the conditional is also an
l-value, as you've discovered. If either expression was not an l-value,
then the one that was an l-value would be subject to an "lvalue to rvalue
conversion", and the result of the expression would be an r-value and not
assignable.

Now that makes sence. Thank you very much.

As for the other examples. I did find the example with the functions a
little surprising but not unexpected as the identifiers are linked to
referances to the functions. I did however expect the behavior with
pointers. In fact my plan had been to replace the above code with pointers
and I fully expected:

int *a, *b;

*((*a < *b) ? a : b) = 0;

To work. Everything I *thought* that I understood about the conditional
operator told me that it would.

What is kind of ammusing in all of this is that I was writing a tutorial on
using the conditional operator. Most such tutorials just give the basic
syntax and the standard example. The problem is that they never really point
out that the conditional operator is an *operator* and not just a shorthand
for an if-else *statement*. So I was looking for surprising examples that
would help drive this point home. -- And as you can see when I tried what I
*thought* would be a counter example it turned into an example. :)

If you know of a good resource where I could find some more technical data
or surprising examples on the conditional operator in C/C++/C# I would
greatly apreciate it.

Again thank you for clearing up the rvalue-lvalue. I will do more research
on that so that I can explain in detail.
 
G

Guest

Regarding the 'other surprising cases'; are you sure there's no weird
compiler setting you have to have to get these to work?
I tried the following (your first case) and it won't compile:
class foo {
void test() {
int a, b;
(a < b ? f : g)(10);
}
void f(int i) {}
void g(int i) {}
};

--
David Anton
www.tangiblesoftwaresolutions.com
Instant C#: VB to C# converter
Instant VB: C# to VB converter
Instant C++: C#/VB to C++ converter
Instant Python: C#/VB to Python converter


Carl Daniel said:
Nicholas said:
I was just thinking that I understood the conditional operator when I
coded the following expecting it to fail:

int a= 10, b= 20, c= 0;
((a < b) ? a : b) = c; // a=0
a=20; b= 10;
((a < b) ? a : b) = c; //b=0

Now since the expresion 5 = c is not valid I did not expect the above
to work. But it does work. Why? I thought that the ?: operator would
evaluate to the value of "a" or "b" not to some kind of pointer to
the value. This really leaves me wondering how the operator works.

This results from the concepts of "lvalues" and "rvalues". (Basically, an
"lvalue" can be assigned-to, while an "rvalue" cannot).

The conditional operator returns one of two expressions. If both
expressions are l-values, then the result of the conditional is also an
l-value, as you've discovered. If either expression was not an l-value,
then the one that was an l-value would be subject to an "lvalue to rvalue
conversion", and the result of the expression would be an r-value and not
assignable.

int a, b;
((a < b ) ? a : 5) = 1; // won't compile

You'll find the conditional operator works in other possibly surprising
cases as well:

void f(int) { ... }
void g(int) { ... }

int a,b;

((a < b) ? f : g)(10); // calls either f or g with parameter 10


int p[100], q[100];

((a < b) ? p : q)[10] = 0; // zero out 10th element of *p or *q

-cd
 
N

Nicholas M. Makin

David Anton said:
Regarding the 'other surprising cases'; are you sure there's no weird
compiler setting you have to have to get these to work?
I tried the following (your first case) and it won't compile:
class foo {
void test() {
int a, b;
(a < b ? f : g)(10);
}
void f(int i) {}
void g(int i) {}
};

--
David Anton
www.tangiblesoftwaresolutions.com
Instant C#: VB to C# converter
Instant VB: C# to VB converter
Instant C++: C#/VB to C++ converter
Instant Python: C#/VB to Python converter

Your class example also did not compile for me. However when I deleted the
class construct and moved the definitions for f() & g() to before test it
did compile:

int f(int i) { return i - 1;}
int g(int i) { return i + 1;}

int test(int i) {
int a=1, b=0;
return ((a < b) ? f : g)(i);
}

(I changed the return types to make sure it worked) and worked
wonderfully...

I am confused on why this didn't work from within the class. I did try a few
permutations of things to see if I could get it to work.

class foo {
int f(int i) { return i - 1;}
int g(int i) { return i + 1;}

int test(int i) {
int a=1, b=0;
return ((a < b) ? f : g)(i);
}
};

returns the error: "term does not evaluate to a function" in VC++ (VS6)
and even making it: ((a < b) ? foo::f : foo::g)(i);
did not resolve the problem.

and foo::((a < b) ? f : g)(i); is an nonsencical as it looks.

Even tried to define all of the functions outside of the class declaration
and it generates the same error.

Just when I think I am getting to the point where I understand things!!!
 
N

Nicholas M. Makin

The following construction also works:

class foo {
static int f(int i) { return i - 1;}
static int g(int i) { return i - 1;}
int test(int i){
int a=1, b=0;
return ((a < b) ? f : g)(i);
}
};

I feel that this has to be a hint as to why the other construction did not
work, but I can't quite figure it out. I tried various versions using
pointers to functions and found the same error in the end. I feel as though
the answer is simple and just escapeing my grasp.

I also tried using this->f and this->g which did no better.
 
C

Carl Daniel [VC++ MVP]

Nicholas said:
The following construction also works:

class foo {
static int f(int i) { return i - 1;}
static int g(int i) { return i - 1;}
int test(int i){
int a=1, b=0;
return ((a < b) ? f : g)(i);
}
};

I feel that this has to be a hint as to why the other construction
did not work, but I can't quite figure it out. I tried various
versions using pointers to functions and found the same error in the
end. I feel as though the answer is simple and just escapeing my
grasp.
I also tried using this->f and this->g which did no better.

This oddity is due to the fact that a non-member function or static member
function can have it's address taken by simply referring to the name and
that address is a simple function pointer. That is not true for an ordinary
member function. The corresponding (extremely ugly) case for a non-member
function would be something like:


class X
{
int f(int);
int g(int);

int h(bool b, int a)
{
return (this->*(b ? &X::f : &X::g))(a);
}
};

-cd
 
G

Guest

Thanks for clearing that up Carl!
I'm sure very few people write code like that (since the 'if/else' is so
much simpler in this case), but if we ever come across it we'll know exactly
what's happening.
--
David Anton
www.tangiblesoftwaresolutions.com
Instant C#: VB to C# converter
Instant VB: C# to VB converter
Instant C++: C#/VB to C++ converter
Instant Python: C#/VB to Python converter
 
C

Carl Daniel [VC++ MVP]

David said:
Thanks for clearing that up Carl!
I'm sure very few people write code like that (since the 'if/else' is
so much simpler in this case), but if we ever come across it we'll
know exactly what's happening.

Honestly, I can scarcely imagine a situation in which it'd be acceptable to
write code like that. It's just too obscure. I wouldn't expect the
compiler to do anything resembling optimization on such a construct either
(but who knows - the optimizer guys pull some amazing stuff out of their
hats).

-cd
 
C

Carl Daniel [VC++ MVP]

Carl said:
true for an ordinary member function. The corresponding (extremely
ugly) case for a non-member function would be something like:

Oops! That should say "non-static member function" in the line above.

-cd
 
G

Guest

I noticed that, but after your fantastic explanation I wasn't going to
nitpick ;)
--
David Anton
www.tangiblesoftwaresolutions.com
Instant C#: VB to C# converter
Instant VB: C# to VB converter
Instant C++: C#/VB to C++ converter
Instant Python: C#/VB to Python converter
 
N

Nicholas M. Makin

In the end this is what I got working.

class foo {
public:
int f(int i);
int g(int i);
int test(int i);
};

int foo::f(int i) { return i - 1;}
int foo::g(int i) { return i + 1;}

int foo::test(int i) {
int a=1, b=0;
return (this->*((a < b) ? this->f : this->g))(i);
}

I was drawn to it by the error messages generated by various other attempts.
The part that took the longest was the outside parentheses (a little lesson
in what left-to-right association vs right-to-left association). I have to
say I don't ever plan on using such a construction, but the process has been
very instructive.

Thank you both very much for the adventure.
 
C

Carl Daniel [VC++ MVP]

Nicholas said:
In the end this is what I got working.

class foo {
public:
int f(int i);
int g(int i);
int test(int i);
};

int foo::f(int i) { return i - 1;}
int foo::g(int i) { return i + 1;}

int foo::test(int i) {
int a=1, b=0;
return (this->*((a < b) ? this->f : this->g))(i);
}

I was drawn to it by the error messages generated by various other
attempts. The part that took the longest was the outside parentheses
(a little lesson in what left-to-right association vs right-to-left
association). I have to say I don't ever plan on using such a
construction, but the process has been very instructive.

That actually shouldn't compile - it's not legal C++. Indeed, it doesn't
compile with VC8 or Comeau C++ (the gold standard for C++ legality).

Instead, it should read:

class foo {
public:
int f(int i);
int g(int i);
int test(int i);
};

int foo::f(int i) { return i - 1;}
int foo::g(int i) { return i + 1;}

int foo::test(int i) {
int a=1, b=0;
return (this->*((a < b) ? &foo::f : &foo::g))(i);
}

Which does compile with VC8 (VS 2005). I assume you're using an older
version of the compiler that didn't get this right.

-cd
 

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

Top