5

Is a && b && c defined by the language to mean (a && b) && c or a && (b && c)?

Wow, Jerry was quick. To beef up the question: does it actually matter? Would there be an observable difference between a && b && c being interpreted as (a && b) && c or a && (b && c)?

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • 4
    Yes, it matters. Short-circuting would prevent some of your operands from being evaluated. – user229044 Nov 18 '13 at 16:44
  • @meagar How would `(a && b) && c` differ from `a && (b && c)` with respect to short-circuiting? For example, how would `(false && b) && c` differ from `false && (b && c)`? Both expressions evaluate neither `b` nor `c`... – fredoverflow Nov 18 '13 at 16:50
  • 1
    @FredOverflow if `a` is false, you are correct. if `b` is false, `c` never gets evaluated. Basically, if there is some `false` to the left of some AND'd conditions, the tests stop with it. – Zac Howland Nov 18 '13 at 16:52
  • @ZacHowland: If `a` is true, and `b` is `false`, `c` doesn't get evaluated, regardless of whether you use `(a && b) && c` or `a && (b && c)` – Benjamin Lindley Nov 18 '13 at 16:55
  • @BenjaminLindley Isn't that what I just said? – Zac Howland Nov 18 '13 at 16:57
  • @ZacHowland: Did you? I assumed you were stating that for only one of the two expressions, because your answer seems to indicate that you are of the opinion that associativity matters because of short-circuiting. But if what I said in my comment is true, it does not. – Benjamin Lindley Nov 18 '13 at 17:00
  • @BenjaminLindley I've updated my answer to be more clear. What I was stating in the previous comment was simply that if you operate from left to right, the first `false` stops anything further right from being tested/executed. With that in mind, it doesn't matter if you write `(a && b) && c` or `a && (b && c)` or `a && b && c` as they are all equivalent. The only thing that does matter is the order of the operands. – Zac Howland Nov 18 '13 at 17:04

4 Answers4

10

§5.14/1: "The && operator groups left-to-right. [...] Unlike &, && guarantees left-to-right evaluation: the second operand is not evaluated if the first operand is false."

As to when or how it matters: I'm not sure it really does for built-in types. It's possible, however, to overload it in a way that would make it matter. For example:

#include <iostream>

class A;

class M {
    int x;
public:
    M(int x) : x(x) {}
    M &operator&&(M const &r); 
    M &operator&&(A const &r); 
    friend class A;
};

class A {
    int x;
    public:
    A(int x) : x(x) {}
    A &operator&&(M const &r); 
    A &operator&&(A const &r);
    operator int() { return x;}
    friend class M;
};

M & M::operator&&(M const &r) {
    x *= r.x;
    return *this;
}

M & M::operator&&(A const &r) {
    x *= r.x;
    return *this;
}

A &A::operator&&(M const &r) {
    x += r.x;
    return *this;
}

A &A::operator&&(A const &r) {
    x += r.x;
    return *this;
}

int main() {
    A a(2), b(3);
    M c(4);

    std::cout << ((a && b) && c) << "\n";
    std::cout << (a && (b && c)) << "\n";
}

Result:

9
16

Caveat: this only shows how it can be made to matter. I'm not particularly recommending that anybody do so, only showing that if you want to badly enough, you can create a situation in which it makes a difference.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
5

In fact it is very important that the expression is computed from left to right. This is used to have short-circuit in some expressions. Here is a case where it does matter:

vector<vector<int> > a;
if (!a.empty()  && !a[0].empty() && a[0].back() == 3) 

I bet you write similar statements few times a day. And if associativity was not defined you would be in huge trouble.

user229044
  • 232,980
  • 40
  • 330
  • 338
Ivaylo Strandjev
  • 69,226
  • 18
  • 123
  • 176
  • @FredOverflow right you are.I have added a way more practical and in fact common case. – Ivaylo Strandjev Nov 18 '13 at 16:49
  • If you add explicit parentheses to change the associativity of the expression, i.e. `if (!a.empty() && (!a[0].empty() && a[0].back() == 3))` -- does it make a difference? I don't think it does. – Benjamin Lindley Nov 18 '13 at 16:52
  • @BenjaminLindley no it does not. I believe my answer addresses an earlier version of the question where if the expression is evaluated left to right or right to left was included – Ivaylo Strandjev Nov 18 '13 at 16:56
5

The && and || operators short-circuit: if the operand on the left determines the result of the overall expression, the operand on the right will not even be evaluated.

Therefore, a mathematician would describe them as left-associative, e.g.
a && b && c(a && b) && c, because to a mathematician that means a and b are considered first. For pedagogical purposes, though, it might be useful to write a && (b && c) instead, to emphasize that neither b nor c will be evaluated if a is false.

Parentheses in C only change evaluation order when they override precedence. Both a && (b && c)
and (a && b) && c will be evaluated as a first, then b, then c. Similarly, the evaluation order of both
a + (b + c) and (a + b) + c is unspecified. Contrast a + (b * c) versus (a + b) * c, where the compiler is still free to evaluate a, b, and c in any order, but the parentheses determine whether the multiplication or the addition happens first. Also contrast FORTRAN, where in at least some cases parenthesized expressions must be evaluated first.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • Er, if by "it does not matter" you mean "`a && b && c` is evaluated strictly left to right regardless of parenthesization", then yes. See edit. – zwol Nov 18 '13 at 16:58
2

If a && b is false, then the && c portion is never tested. So yes, it does matter (at least in that you need to have your operations ordered from left to right). && is an associative operation by nature.

Associative Property

Within an expression containing two or more occurrences in a row of the same associative operator, the order in which the operations are performed does not matter as long as the sequence of the operands is not changed. That is, rearranging the parentheses in such an expression will not change its value.

So it doesn't matter (logically) if you write it as a && (b && c) or (a && b) && c. Both are equivalent. But you cannot change the order of the operations (e.g a && c && b is not equivalent to a && b && c).

Zac Howland
  • 15,777
  • 1
  • 26
  • 42
  • How would `(false && b) && c` differ from `false && (b && c)`? Both expressions evaluate neither `b` nor `c`... – fredoverflow Nov 18 '13 at 16:51
  • In that case, you are correct. The logical flow is simply from left to right, so when you reach your first `false` condition in a chain of conditions, it stops there. Since `&&` is associative by nature, as long as you don't throw an `||` in there, it won't make much of a difference. As soon as you have an `||` condition, the parentheses start to matter. – Zac Howland Nov 18 '13 at 16:54