2

C++ has logical operations, !a, a && b, a || b applicable to true/false values. And it has bitwise operations, applicable to all (integer?) types, ~a, a & b, a | b, a ^ b, (a << N, a >> N). Together with in place mutation operators, a &= b, a |= b, a ^= b, (a <<= b, a >>= b).

What confuses me is that bitwise operations could be acting on bits of bool that are not at all part of the true/false value. Even doing unnecessary bit operations. I guess that last fact makes bitwise shifting undefined for bool and that makes me doubt whether all bitwise operations are valid at all.

So, are these equivalent for bool types? ~a == !a, a && b == a & b, a || b == a | b?

For some reason (won't question here) there is no "in-place" logical mutation operators (i.e. a &&= b, a ||= b).

This inspires these questions, are these other bitwise operations valid on bools themselves? are a &= b and a |= b always equivalent to a = a && b and a = a || b respectively?** (no "in-place" xor a = a ^ b?)

Some examples: https://godbolt.org/z/hcccG8c9o

alfC
  • 14,261
  • 4
  • 67
  • 118
  • 1
    Does this answer your question: https://stackoverflow.com/q/32800866/104458 – selbie Feb 11 '23 at 08:41
  • @selbie, I extrapolate that in any bitwise operation `bool` is promoted to `int` (why not `char`?), and that gives a path to evaluate a response to my questions. Which I guess is positive in all cases but I am bad to take into account corner cases. – alfC Feb 11 '23 at 08:48
  • 2
    except for short circuit behavior. – apple apple Feb 11 '23 at 09:00
  • 1
    trial and error shouldnt be the last but it can be the first step to understanding. I don't quite understand your asserts. You can compare bit and bool side by side: https://godbolt.org/z/jsE6bdhqd. What you said, promotion to integer messes up `~` – 463035818_is_not_an_ai Feb 11 '23 at 09:06
  • yep, I don't understand completelly why. It is not a matter of converting back to `bool` even. https://godbolt.org/z/nd1h1P1M4 – alfC Feb 11 '23 at 09:13
  • 1
    @alfC -- re "why not `char` -- **all** integer types that are smaller than `int` are promoted to `int` (or `unsigned int`, but that's not relevant here) when they are used in expressions. So promoting `bool` to `char` would just be an extra step on the way to `int`. – Pete Becker Feb 11 '23 at 14:02

1 Answers1

5

(For simplicity I will assume C++20, i.e. two's complement representation of integer types. What I am writing may technically not hold prior to C++20 for all theoretically possible conforming representations of integer types.)

What confuses me is that bitwise operations could be acting on bits of bool that are not at all part of the true/false value.

The built-in bitwise operations have the usual arithmetic conversions applied to their operands first. For integral operands that implies integral promotions: Operands with a conversion rank below that of int are converted to int first (or unsigned int or a higher rank integral type if int can't hold all values of the original type).

The promotion always leaves the original value unchanged. In the case of bool, true is mapped to 1 and false to 0. So the result after promotion will have its least significant bit either set or unset, while all other bits are unset. Also, there are always at least 15 such bits, because int must be at least 16 bit wide.

As a consequence ~a == !a is always false. ~ will set these additional bits, while promotion of !a will not. However bool(~a) == bool(!a) is true only if a is false, because with additional bits set bool(~a) is always true.

a && b == a & b is always true, because additional bits will be zero when & is applied.

a || b == a | b is always true, because additional bits will again remain zero.

However, if a and b are actual expressions, then there is another difference in that the logical operators short-circuit evaluation, while the bitwise ones don't. Therefore, the side effects may be different.

Also, the result of the logical operators will be a bool, while that of the bitwise ones will be a int. Therefore they are not interchangeable as they might e.g. affect overload resolution where their result is used.

Also, this applies only for the built-in operators. If any overloaded operators are involved, nothing is guaranteed.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • I had the feeling that there was some counterintuitive case. ~a not equivalent to !a is very much so. – alfC Feb 11 '23 at 09:24
  • "If any overloaded operators are involved, nothing is guaranteed." , that is exactly the origin of my question, it was to implement operator overloading in a way that is logically (and/or bitwise) consistent. – alfC Feb 11 '23 at 09:25
  • @alfC In terms of value you are basically good with `&` and `|`, but not `~`. Still, the short-circuit behavior is significant, as is the fact that the compound assignment operators evaluate the left-hand side only _once_. – user17732522 Feb 11 '23 at 09:37
  • @alfC I would not attempt to change the meaning of bitwise and logical operators to fit one paradigm. That's very counter-intuitive. Either keep the standard meaning or don't overload the ones not matching the paradigm you want to follow. – user17732522 Feb 11 '23 at 09:39
  • I abuse operator overloading often, (including (unary) `&`, `~`, and `,`) (I can point you to these). But as much as I do that, I never overload `&&`, `||` because I deem that as "impossible"/"always wrong" because logical shortcutting cannot be simulated by operator overloading. – alfC Feb 11 '23 at 09:46
  • operator overloading is a cool feature until you discover that not operator overloading is pretty cool as well ;) – 463035818_is_not_an_ai Feb 11 '23 at 09:52
  • @463035818_is_not_a_number, I couldn't agree more. Both are cool. If you are going to do it, you have to do it all the way: https://gitlab.com/correaa/boost-multi/-/blob/master/README.md#pointer-to-subarray – alfC Feb 11 '23 at 10:19
  • @463035818_is_not_a_number Obviously `&(&*((~A).begin()+1))->operator()(2) == &A[2][1]`, https://godbolt.org/z/YKeK85ofo – alfC Feb 11 '23 at 10:36