Author: Chloé Lourseyre

I often happen to see, on pretty old projects, the operators `&=` and `|=` used to update a boolean indicating success that is returned along successive function calls. As in the following example :

```bool success = true;
success &= DoOneThing();
success &= DoOneOtherThing();
success &= DoALastThing();
if (success)
std::cout << "Success !" << std::endl;
```

This way of writting is more concise than the usual :

```bool success = true;
success = success && DoOneThing();
success = success && DoOneOtherThing();
success = success && DoALastThing();
if (success)
std::cout << "Success !" << std::endl;
```

But you should be cautious, because behind the operators `&=` and `|=` hide a few subtelties that one should be aware of.

Definition of the operators &= and |=

First, let’s see precise definition of `&=` and `|=`. In this section, I’ll only use `&=` as an example, but the behavious of `|=` is pretty much the same.

`&=` is an arithmetic binary operator, which permforms a binary AND and its assignation. In other terms, writting this :

```v1 &= v2;
```

is equivalent to this :

```v1 = v1 & v2;
```

except that v1 is only evaluated once.

For more insight about this, read the C++11 standard section `5.17 Assignment and compound assignment operators` or go to https://en.cppreference.com/w/cpp/language/operator_arithmetic

What we call “arithmetic binary operators” (which use the symbols `&`, `|`, `^`, `<<` and `>>`) are operators that perform bit-to-bit operations.

For instance :

```int a = 0b110;
int b = 0b011;
int c = a & b; // AND - 010
int d = a | b; // OR  - 111
int e = a ^ b; // XOR - 101
```

Writting a &= b; is thus performing a bit-to-bit AND operation on a and b, and assigns the result to a.

Differences between &= and &&, |= and ||

The operators `&&` and `||` are, as for them, logical operators, as opposed to `&=` and `|=` who are arithmetic operators.

The use of `&=` and `|=` in a logical context is prone to diverge from what we could expect them to do.

Here are two example to illustrate that.

Example 1

```#include <iostream>

bool foo()
{
std::cout << "call foo()" << std::endl;
return true;
}

int main() {
bool c = true;
std::cout << "Using |=" << std::endl;
c |= foo();
std::cout << "Using = and ||" << std::endl;
c = c || foo();
std::cout << "End" << std::endl;
return 0;
}
```

The function `foo()` always returns `true`, as long as writing on the standard output to indicate it’s been called.

In the `main()` function, we create a bool `c`, initialized true, on which we will perform the arithmetic binary operator `|=` and the logical operator `||`.

Here is the standard output :

``````Using |=
call foo()
Using = and ||
End``````

We can plainly see that in the |= case, foo() is called whereas it is not in the || case.

This is simply due to the fact that, in C++, logical operators are lazy. This means that if the right-hand-side of the operation can not change its result after evaluating the left-hand-side, then it won’t be evaluated.

Here, because `c` is already `true` in the expression `c || foo()`, no matter the value of `foo()` the result of the operation will always be `true`.

In the case of `|=`, it is a bit different. `|=` is not a logical operator, it does not reason in terms of `true` or `false`, but it performs arithmetic operations on integers. Thus, it needs to evaluate each side of the operation before evaluating the result. With arithmetic operations, both side are always evaluated.

Whether you wish the right-hand-side to be evaluated depends on the context. However, keep in mind that when C++ developpers see logical-like operations, they expect it to be lazy.

Example 2

The following example, though quite uncommon in practice, can lead to errors that are difficult to investigate.

```#include <iostream>

int main() {
int c1 = true;
int c2 = 0b10;

if (c1)
std::cout << "c1 is true" <<std::endl;
else
std::cout << "c1 is false" <<std::endl;

if (c2)
std::cout << "c2 is true" <<std::endl;
else
std::cout << "c2 is false" <<std::endl;

if (c1&&c2)
std::cout << "c1&&c2 is true" <<std::endl;
else
std::cout << "c1&&c2 is false" <<std::endl;

c1 &= c2;

if (c1)
std::cout << "c1&=c2 -> c1 is true" <<std::endl;
else
std::cout << "c1&=c2 -> c1 is false" <<std::endl;

return 0;
}
```

Here, two variables are created : `c1` which is initialized to `true`, and `c2`, which is initialized to `0b10`.

Following four blocs performing logical evaluations :

• First we logically evaluate `c1`.
• Then, we similarly evaluate `c2`.
• Then we evaluate the logical operation `c1 && c2`.
• Finaly, we realise the arithmetic binary operation `c1 &= c2` just before evaluating `c1` again.

Here is the standard output of this code :

``````c1 is true
c2 is true
c1&&c2 is true
c1&=c2 -> c1 is false``````

Let’s see in details how this works :

First of all, `c1` is considered `true`. This is expected because it is explicitly initiliazed with `true`.

Secondly, `c2` is also considered `true`. This is so because, in C++, any non-null integer evaluates to `true`.

Thirdly, `c1 && c2` is evaluated to `true`. Indeed, because `c1` and `c2` and individually `true`, performing the AND operation on them is logically `true`.

Fourthly, we perform `c1 &= c2;`. By doing so, we actually perform `c1 & c2` and affect the result to `c1`.

However, according to the standard, `true` is equal to `1` when promoted to integer. Thus, `c1 & c2` is equivalent to `0b01 & 0b10`. The result to this operation really is `0b00`, the bit-to-bit AND operation is false for every bit.

The `0` value being evaluated to false in a logical expression, `c1` is, after all, `false`.

You won’t see this happen very often in live code, but there is plenty of situations, especially in legacy code, when an integer value that is not `1` is returned as if it was a true boolean.

Conclusion

The use of &= and |= will do what we expect in 99% of the case. But in the remaining 1%, you’ll have major bugs difficult to investigate.

Moreover, I’d like to pinpoint a semantic problem here. The fact that we ue an arithmetic operation instead of a logical one mislead the reader and forces them to do an additional effort to pull this straight. In addition to that, it may prevent the compiler to perform optimization.

Writting `a = a && b;` instead of `a &= b;` is not much an effort, with only a few additional characters, but it will protect you from bugs and hold better semantics.

Thank you for your attention and meeting you next week !

NB : The example were done with clang and the compiler options `--std=c++20 -O3`

Author: Chloé Lourseyre