-1

I was messing around with some c++ code after learning that you cannot increment booleans in the standard. I thought incrementing booleans would be useful because I like compacting functions if I can and

unsigned char b = 0; //type doesn't really matter assuming no overflow
while (...) {   
    if (b++) //do something
...}

is sometimes useful to have, but it would be nice to not have to worry about integer overflow. Since booleans cannot take on any value besides 0 or 1, I thought that perhaps this would work. Of course, you could just have a boolean assigned to 1 after you do your operation but that takes an extra line of code.

Anyway, I thought about how I might achieve this a different way and I came up with this.

bool b = 0;
while (...)
if (b & (b=1)) ...

This turned out to always evaluate to true, even on the first pass through. I thought - sure, its just doing the bitwise operator from right to left, except that when I swapped the order, it did the same thing. So my question is, how is this expression being evaluated so that it simplifies to always being true?

As an aside, the way to do what I wanted is like this I guess:

bool b = 0;
while (...) if (!b && !(b=1)) //code executes every time except the first iteration

There's a way to get it to only execute once, also.

Or just do the actually readable and obvious solution.

bool b = 0;
while (...) {
  if (b) {} else {b=1;}
}
  • In `b & (b=1)`, the assignment is evaluated before the bitwise `&`, and the value of the assignment expression is 1, too. So, both operands of `&` are 1. Also, please select either C or C++ tags but not both, as those are two different languages. – DYZ Jul 23 '20 at 07:33
  • 2
    Isn't this just UB due to a modification within a sequence point? – paddy Jul 23 '20 at 07:37
  • 2
    `b & (b=1)` is undefined behaviour in C. – Lxer Lx Jul 23 '20 at 07:38
  • 1
    FYI, GCC and Clang both [warn about it](https://gcc.godbolt.org/z/dere3e). – chris Jul 23 '20 at 07:44
  • It seems that there is a complete confusion between logical and bitwise operators in your question. –  Jul 23 '20 at 07:58
  • I understand logical and bitwise operators, I just figured that the bitwise operators would evaluate left-to-right and since booleans can only be 0 or 1, they would otherwise be similar. When they didn't evaluate either right-to-left or left-to-right consistently, I was surprised. But I suppose this was a stupid question to begin with, for other reasons. – Magnus Anderson Jul 23 '20 at 22:18

3 Answers3

2

This is what std::exchange is for:

if (std::exchange(b, true)) {
    // b was already true
}

As mentioned, sequencing rules mean that b & (b=1) is undefined behaviour in both C and C++. You can't modify a variable and read from it "without proper separation" (e.g., as separate statements).

chris
  • 60,560
  • 13
  • 143
  • 205
0

I don't see what meaning you can assign to a "bitwise increment" operator. If I am right, what you achieve is just setting the low-order bit, which is done by b|= 1 (following the C conventions, this could have been defined as a unary b||, but the || was assigned another purpose). But I don't feel that this is a useful bitwise operation, and it can increment only once. IMO the standard increment operator is more bitwise.

If your goal was in fact to "increment" a boolean variable, i.e. set it to true (prefix or postfix), b= b || true is a way. (There is no "logical OR assignement" b||= true, and even less a "logical increment" b||||.)

0

There is no "strange behavior" here. The & operator, in your case is a bitwise arithmetic operator. These operator are commutative, so both operand must be evaluated before computing the resulting value.

As mentioned earlier, this is considered as undefined behavior, as the standard does not prevent computing + loading into a register the left operand, before doing the same to the second, or doing so in reverse order.

If you are using C++14 or above, you could use std::exchange do achieve this, or re-implement it quite easily using the following :

bool exchange_bool(bool &var, bool &&val) {
    bool ret = var;
    var = val;
    return ret;
}

If you end up re-implementing it, consider using template instead of hard-coded types.

As a proper answer to your question :

So my question is, how is this expression being evaluated so that it simplifies to always being true?

It does not simplify, if we strictly follow the standard, but I'd guess you are always using the same compiler, which end up producing the same output.

Side point: You should not "compact" functions. Most of the time the price of this is less readability. This might be fine if you're the only dev working on a project, but on bigger project, or if you come back to your code later, that could be an issue. Last but not least, "compact" function are more error prone, because of their lack of readability. If you really want shorter function, you should consider using sub-function that will handle part of the function computation. This would enable you to write short function, to quickly fix or update behavior, without impacting clarity.

Phantomas
  • 206
  • 1
  • 6
  • Thank you for the thorough answer. And about the last point, most of the code I write is for personal projects or classwork where the code is never read. But I suppose that attitude won't help me in the long run. – Magnus Anderson Jul 23 '20 at 22:22