The two cases will most likely yield the very same machine code, or something very close. So which one to pick is a matter of coding style.
I would say that if the operations are complex, so that they will not easily fit on one single line, then the if
-else if
is to prefer. Otherwise, if the operations are somewhat simple, the ||
version is probably to prefer. It is rather subjective though.
Advanced discussion about the inner workings of these operators follows. You may stop reading here if you aren't terribly interested about such things!
However, there are some details that make the two versions ever so slightly different. In both cases, each individual operand is implicitly promoted using type balancing. But in the case of ||
, there is an extra implicit promotion taking place:
The result of (!x && y && z)
is balanced against the result of (x && !y && !z)
. In the end, the result is of the type of the operand with biggest conversion rank.
Does it matter? I can't see how it would in this specific case. But lets say we have something like this:
uint8_t x;
uint8_t y;
uint32_t z;
And then we have an expression:
if( (x && y) || (x && z) )
// or
if(x && y)
{}
else if(x && z)
{}
Lets assume we are using a 8 or 16 bit CPU where int
is 16 bits.
In the if-else case, the following happens:
- x and y are both integer promoted from
uint8_t
to int
, since they are small integer types.
- The expression
(int)x && (int)y
is balanced. x && y
is calculated on the type int
and the result is of type int
.
- In the expression
x && z
, x
is integer promoted as above, but not z
, it is not a small integer type and remains uint32_t
.
- The expression
(int)x && (uint32_t)z
is balanced and then calculated on the type uint32_t
. The result is of type uint32_t
.
- Thus
x && y
is always calculated on a 16 bit variable and x && z
is always calculated on a 32 bit variable.
If we look at the ||
version, the same things happen. But then we have another, additional balancing: (uint16_t)first_result || (uint32_t)second_result
. This balancing is enforced because of the ||
operator. Which means that if x==true
and y==true
, then result of the whole operation will be of type uint32_t
.
But in the if-else version, the x && y
type was always uint16_t
. The ||
version introduced an obscure tight coupling between the type of x
and the type of z
.
Whether or not the compiler is able to efficiently optimize this ||
scenario, I have no idea. I would expect it to, but since the compiler can make no compile-time decisions about what the result of the whole expression will be, since it can't know if the operands are true or false, it might just end up executing it all on 32-bit types. And that is bad news if we had an 8 or 16 bit CPU, which will handle 32 bit number very inefficiently.