Decisions in if-statements often have to be based on one or more conditions evaluating to (logically) true or false. There are three logical operators in C++ which operate on expressions and yield a boolean result; they are “and” (&&
), “or” (||
) and “not” (!
). Modern C++ allows the keywords and
, or
and not
to be used instead of the more traditional symbolic versions; there is no difference between the two and the keywords may even be implemented as macros.
Both of the binary operators (“or” and “and”) “short-circuit” which means that the value of the first operand determines whether the second is evaluated at all:
bool f() {
cout << "f()\n";
return false;
}
bool t() {
cout << "t()\n";
return !false;
}
auto b1 = f() && t(); // result false, t() not even called
auto b2 = t() || f(); // result true, f() not even called
Interestingly, the use of the bitwise operators (each with a single symbol instead of a double) instead is equally valid here, and bypasses the short-circuit behavior:
auto b3 = f() & t(); // result as b1, both f() and t() are called
auto b3 = t() | f(); // result as b2, both f() and t() are called
For this to work equivalently, both operands must be explicitly bool
, not just implicitly convertible (values such as 0.0
and nullptr
convert to false
implicitly). Another way ensuring that both functions are always called would be to assign them to different variables, and apply the logical operators to these variables.
Before we write off short-circuit evaluation completely, an example of where it is not just desirable but necessary is testing a pointer against false
, and only dereferencing it if non-nullptr
:
void print_chars(const char *p) {
if (p && *p) {
cout << p << '\n';
}
}
print_chars(nullptr); // no output
print_chars(""); // again, no output - can you work out why?
print_chars("Booleans are awesome!");
// outputs this message followed by a newline
The bitwise operators work on any integer type, and also std::bitset
. Where both operands have a number of set bits, as opposed to just the least significant bit, bits at equal positions are compared. They have the names bitand
(&
), bitor
(|
), bitxor
(^
) and compl
(~
). Here are some examples using two four-bit values a = 0b0110
and b = 0b1010
:
Operation | Modern C++ style | Result |
~a | compl a | 0b1001 |
a & b | a bitand b | 0b0100 |
a | b | a bitor b | 0b1110 |
a ^ b | a bitxor b | 0b1100 |
Just a quick note on bitxor
, it follows that the expression a ^ a
is always zero regardless of the value of a
. This means that “exclusive-or” operations are always reversible, which is why they are often used in the generation of hash-values and for encryption methods.
In summary, with logical condition tests conversion of both operands to boolean are implied and the second operand is not always evaluated. With bitwise tests the operands can be boolean or integer, and in the case of the latter all bits are significant.