71 Advanced The Hundred
&& Without the Shortcut
two ampersands, zero protection
#include <iostream>
struct Check {
bool ok;
const char* msg;
};
Check operator&&(Check a, Check b) { return a.ok ? b : a; }
Check user_exists() {
std::cout << "looking up user... not found\n";
return {false, "no such user"};
}
Check charge_card() {
std::cout << "charging card!\n";
return {true, "payment accepted"};
}
int main() {
Check result = user_exists() && charge_card();
std::cout << "result: " << result.msg << "\n";
}
Run it. Does the card get charged?
Answer
Yes. charging card! prints even though the lookup failed — yet the result still says no such user.
Why¶
Built-in && short-circuits: the right side runs only when the left is true — the whole
reason ptr && ptr->next is safe. An overloaded && is an ordinary function call:
user_exists() && charge_card() means operator&&(user_exists(), charge_card()), and a
function needs both arguments evaluated before it can be called. No overload body,
however clever, can peek at the left value and skip the right — skipping would have to
happen at the call site, and call sites don't skip arguments. C++17 fixed the ordering
(the left operand is now evaluated first; before that it was unspecified), but that is
all it fixed — short-circuiting is never restored. operator|| loses short-circuiting
the same way; the overloaded comma lost its sequencing too, until C++17 restored it.
-Wall -Wextra stays silent: this is perfectly legal C++, just perfectly misleading.
The fix¶
Don't overload &&, ||, or the comma. If you want a chaining combinator, take the
right-hand side as a callable, so it only runs on demand:
template <class F>
Check and_then(Check a, F next) { return a.ok ? next() : a; }
Check result = and_then(user_exists(), charge_card); // no () — the card is safe
Takeaway: overloading && keeps the spelling but trades away short-circuiting — its
defining feature — so don't.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo