0

I recently came across a code snippet that looked like this:

bool MyClass::do_work()
{
    bool success = true;
    for (auto const& worker : m_workers)
    {
        success &= worker.do_work();  // worker.do_work() returns a bool
    }
    return success;
}

If I understand it correctly, the function returns true if all workers return true and it returns false if any worker returns false. However, it always evaluates all workers (which is desired). There is no short-circuit evaluation involved, since the bitwise operator &= was used and not the logical operator &&.

Is this behavior guaranteed? To be exact, is it guaranteed that bitwise & always evaluates both operands, even if they are of type bool? I came across many SO answers regarding guaranteed short-circuit evaluation for &&, but none of them state that there is guaranteed non-short-circuit evaluation for &.

If this behavior is guaranteed, is this a good programming style? It took me more than a quick glance to understand the function, because I have not seen this style before, and at first I was confused whether short-circuit evaluation was involved or not.

Is there a better alternative than the following?

bool MyClass::do_work()
{
    bool success = true;
    for (auto const& worker : m_workers)
    {
        if (!worker.do_work())
        {
            success = false;
        }
    }
    return success;
}
Holt
  • 36,600
  • 7
  • 92
  • 139
pschill
  • 5,055
  • 1
  • 21
  • 42
  • 2
    I'd say it's a very confusing style: it seems easy to overlook the no-short-circuit effect. –  Jan 08 '19 at 14:06
  • 3
    All the operands of an operator are usually evaluated, `&&`, `||` and `?:` operators are exceptions. The standard even mentions for `&&` that *"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. – Holt Jan 08 '19 at 15:03
  • @You I don't think the standard guarantees that `all_of` does not perform short-circuit, that would be really strange. – Holt Jan 08 '19 at 15:39
  • @Holt How is `all_of` related to the bitwise `&`? – pschill Jan 08 '19 at 15:41
  • @Holt: I realized this approx. 3 seconds after posting :) – You Jan 08 '19 at 15:41
  • @pschill I was answering a now-deleted comment from [You](https://stackoverflow.com/users/147845/you) ;) – Holt Jan 08 '19 at 15:43
  • 1
    "Is there a better alternative [...]?" [Some](https://sean-parent.stlab.cc/presentations/2013-09-11-cpp-seasoning/cpp-seasoning.pdf) would argue, yes(?): `std::count_if(m_workers.begin(), m_workers.end(), [](const auto& worker) { return !worker.do_work(); }) == 0;` – You Jan 08 '19 at 15:48
  • 3
    Or `success = worker.do_work() && success;` if you want to stick with an imperative loop. – Pete Kirkham Jan 08 '19 at 16:06

1 Answers1

4

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].

Holt
  • 36,600
  • 7
  • 92
  • 139
  • _Unlike `&`, `&&` guarantees left-to-right evaluation: the second operand is not evaluated if the first operand is `false`_. Let me rephrase that: _`&` does not guarantee left-to-right evaluation._ Unfortunately, this statement means that the `&` operator may perform short-circuit evaluation, it just isnt guaranteed. – pschill Jan 08 '19 at 15:43
  • 1
    @pschill No, it means that `&` can evaluate the right operand before the left one, as most operators. When you do `A op B`, `B` can be evaluated before `A`, but both will be evaluated, except for `&&`, `||` and `?:`. – Holt Jan 08 '19 at 15:45
  • @pschill The `||` version is a bit better if you'd prefer: *"Unlike |, || guarantees left-to-right evaluation; **moreover**, the second operand is not evaluated if the first operand evaluates to true."* – Holt Jan 08 '19 at 15:46
  • @pschill I've edited my answer to add the standard quote regarding the default behavior of operators that states that all operands of an operator are evaluated unless specified otherwise. – Holt Jan 08 '19 at 15:59
  • Okay, so the point is that operators (except `&&` etc) are evaluated like "normal" C++ functions, which means that all arguments are evaluated. Thank you :) – pschill Jan 09 '19 at 07:49