Is There Actually a Reason Why Overloaded && and || Don't Short Circuit

Is there actually a reason why overloaded && and || don't short circuit?

All design processes result in compromises between mutually incompatible goals. Unfortunately, the design process for the overloaded && operator in C++ produced a confusing end result: that the very feature you want from && -- its short-circuiting behavior -- is omitted.

The details of how that design process ended up in this unfortunate place, those I don't know. It is however relevant to see how a later design process took this unpleasant outcome into account. In C#, the overloaded && operator is short circuiting. How did the designers of C# achieve that?

One of the other answers suggests "lambda lifting". That is:

A && B

could be realized as something morally equivalent to:

operator_&& ( A, ()=> B )

where the second argument uses some mechanism for lazy evaluation so that when evaluated, the side effects and value of the expression are produced. The implementation of the overloaded operator would only do the lazy evaluation when necessary.

This is not what the C# design team did. (Aside: though lambda lifting is what I did when it came time to do expression tree representation of the ?? operator, which requires certain conversion operations to be performed lazily. Describing that in detail would however be a major digression. Suffice to say: lambda lifting works but is sufficiently heavyweight that we wished to avoid it.)

Rather, the C# solution breaks the problem down into two separate problems:

  • should we evaluate the right-hand operand?
  • if the answer to the above was "yes", then how do we combine the two operands?

Therefore the problem is solved by making it illegal to overload && directly. Rather, in C# you must overload two operators, each of which answers one of those two questions.

class C
{
// Is this thing "false-ish"? If yes, we can skip computing the right
// hand size of an &&
public static bool operator false (C c) { whatever }

// If we didn't skip the RHS, how do we combine them?
public static C operator & (C left, C right) { whatever }
...

(Aside: actually, three. C# requires that if operator false is provided then operator true must also be provided, which answers the question: is this thing "true-ish?". Typically there would be no reason to provide only one such operator so C# requires both.)

Consider a statement of the form:

C cresult = cleft && cright;

The compiler generates code for this as thought you had written this pseudo-C#:

C cresult;
C tempLeft = cleft;
cresult = C.false(tempLeft) ? tempLeft : C.&(tempLeft, cright);

As you can see, the left hand side is always evaluated. If it is determined to be "false-ish" then it is the result. Otherwise, the right hand side is evaluated, and the eager user-defined operator & is invoked.

The || operator is defined in the analogous way, as an invocation of operator true and the eager | operator:

cresult = C.true(tempLeft) ? tempLeft : C.|(tempLeft , cright);

By defining all four operators -- true, false, & and | -- C# allows you to not only say cleft && cright but also non-short-circuiting cleft & cright, and also if (cleft) if (cright) ..., and c ? consequence : alternative and while(c), and so on.

Now, I said that all design processes are the result of compromise. Here the C# language designers managed to get short-circuiting && and || right, but doing so requires overloading four operators instead of two, which some people find confusing. The operator true/false feature is one of the least well understood features in C#. The goal of having a sensible and straightforward language that is familiar to C++ users was opposed by the desires to have short circuiting and the desire to not implement lambda lifting or other forms of lazy evaluation. I think that was a reasonable compromise position, but it is important to realize that it is a compromise position. Just a different compromise position than the designers of C++ landed on.

If the subject of language design for such operators interests you, consider reading my series on why C# does not define these operators on nullable Booleans:

http://ericlippert.com/2012/03/26/null-is-not-false-part-one/

Use case for `&&` and `||` operator overloading with regards to short-circuiting

The question is regards to whether this would still be considered losing short-circuit behaviour of the operation.

Yes, you lose the short-circuiting. In the line

c = a&&b;

both a and b are evaluated. This could be important if there is a possibility that evaluating b might be invalid. (Perhaps instead of b, the second operand could be *p where p was initialized as Value<int> * p = nullptr. Do you want to evaluate *nullptr?)

As I see it, it should be at least effectively preserved,

You do get the majority of the performance benefit in your scenario, yes. I'd be wary of calling it "short-circuiting" though (at least without additional qualification), as others would likely misunderstand the intent and infer more than you intend.



Would a use case such as this warrant a "good-enough" practice for overloading these operators?

First of all, this question is tangential to the performance question. Whether or not you should overload && should be based more on how surprising the behavior is to programmers than on how efficient the implementation is. See point 1 of The Three Basic Rules of Operator Overloading in C++. In this respect, I would be surprised to have operator&& return an object that has to be again tested to get a boolean value.

So I would go with: no, this is not a good way to overload the operator. A better approach would be to define a named function that returns this proxy. Also, perhaps instead of having to call a function to evaluate the proxy as a bool, the proxy could define a user-defined conversion function?

Another consideration is over-engineering. You've written an auxiliary template and changed how operator&& is used; for what benefit? Your stated justification is "I can get the lazy evaluation I want by calling getValue() of this proxy instance". However, you can get the exact same functionality without the overhead of a proxy. You just need to make your original operator&& non-recursive.

// overload && operator
template <typename U, typename V>
bool operator&&(Value<U>& a, Value<V>& b) {
return a.getValue() && b.getValue(); // CHANGE: Call getValue to avoid recursion
}

int main()
{
Value<double> a{1.0};
Value<int> b{0}; // CHANGE: initialize with an integer

std::cout << (a&&b) << '\n'; // 0 means false
b.m_val = 1; // update the value of b
std::cout << (a&&b) << '\n'; // 1 means true!!!
}

There might be a reason for your proxy in your full code, but there is no reason to introduce it for this question. You've made the question more complex than necessary, hence less accessible to those who might benefit from it. With the proxy or without, the result is the evaluation of a.getValue() && b.getValue() at the given line. When getValue() returns a type for which operator&& is not overridden, the result is bool and b.getValue() is invoked only when a.getValue() evaluates to truth.

If getValue() returns a type for which operator&& is overridden, then the result might be something other than bool, but at the same time you lose the short-circuiting you've been striving for. So ignore this case? If you need to handle this case, just change the return type in the operator&& template from bool to auto. Exact same end result as your approach with a proxy, but with more familiar syntax and less typing.

As far as this question goes, your approach is over-engineered. So, no, it is not good practice for overloading.

Short circuit of overloaded operator && and || in C++17

That statement is not about short-circuit evaluation. It's about the order of evaluating the operands.

Pre-C++17, the order for evaluating the operands of overloaded && and || was compiler-defined. C++17 defines an explicit order of evaluation of left-to-right for && and ||, whether they are overloaded or not.

Short circuit evaluation still only applies to the built-in operators.

Note that on the actual page you cited, the part that is highlighted is what is applied to a specific version. That part is about the sequencing order, not the part about short-circuit evaluation.

Is there an Non-Short circuited logical and in C++?

The & operator performs logical "and" operation for bool operands and is not short circuited.

It's not a sequence point. You cannot rely on the order of evaluation of the operands. However, it's guaranteed that both operands are evaluated.

I do not recommend doing this. Using temporary variables is a better solution. Don't sacrifice readability for "clever code".

Lazy, overloaded C++ && operator?

You should not overload bool operator&&, since you lose short circuit evaluation, as you have discovered.

The correct approach would be to give your class a bool conversion operator

class MyBool {
public:
bool theValue;
MyBool() {}
MyBool(bool aBool) : theValue(aBool) {}
explicit operator bool() { return theValue; }
};

Note that explicit conversion operators require C++11 compliance. If you do not have this, have a look at the safe bool idiom.

Is it guaranteed that bitwise and for bool does not short circuit?

Unless explicitly specified by the standard, all operands of an operator are evaluated and unsequenced1 in C++:

[intro.execution]

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.
[...]
The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. [...]

The only three exceptions that come to my mind are the &&, || and ?: operators2.

The standard even mentions for &&3 that:

[expr.log.and]

Unlike &, && guarantees left-to-right evaluation: the second operand is not evaluated if the first operand is false.

As for if this is good programming style, this is opinion-based.


1 Unsequenced basically means that if you have A @ B (where @ is an operator), B (and its side effects) can be evaluated before A, which is why construct such as i++ + ++i are undefined behavior.

2 Note that for overloaded && and || operator, this is not true anymore since both operands are evaluated. ?: cannot be overloaded.

3 There is a similar note for | within [expr.log.or].

Short-circuiting of non-booleans

The second expression will evaluate to either 1 or 0.

Quoting the C11 standard draft:

6.5.14 Logical OR operator


  1. The || operator shall yield 1 if either of its operands compare unequal to 0; otherwise, it yields 0. The result has type int.

So the two expressions are very different, since one of them yields a pointer, and the other one an integer.

Edit:

One of the comments claims that this answer is only valid for c, and @Lightness Races in Orbit is right.

There are also answers that are only correct for c++1, although the only difference with them is that c++ has type bool and then it evaluates this expression as bool instead of int. But apparently there is an important issue with overloading || operator in c++, which prvents short-citcuiting to apply for the object that overloads it.

So for c++ there are more things to consider, but since this question was tagged with both languages tags, then it's necessary to mention at least the differece.

The rule still applies when short-circuiting applies, i.e. the result of the evaluation of the expressions is either 1 or 0 for c and true or false for c++.


1 Like these answers: 1, 2



Related Topics



Leave a reply



Submit