0

As an exercise, I would like to check, in a single assert, if I have seen some (boolean) state before. Seeing it never is OK, seeing it once is OK, but seeing it twice should raise an assertion. (To be exact, seeing anything after seeing it once should raise an assertion.)

I have tried assert(!(seen & (seen |= is_here))); - however, this behaves very inconsistently across compilers.

#include <iostream>
#include <assert.h>

int main() {
    bool seen = false;
    bool is_here;

    is_here = false;
    // these shall pass
    assert(!(seen & (seen |= is_here)));
    assert(!(seen & (seen |= is_here)));

    is_here = true;
    // this shall pass, but fails in MSVC
    assert(!(seen & (seen |= is_here)));
    // after that, this shall pass
    assert( (seen & (seen |= is_here)));

    is_here = false;
    // same as this
    assert( (seen & (seen |= is_here)));

    std::cout << "Done.";

    return 0;
}

I think I am hitting a wall of undefined behavior, so I have tried rewriting my expression, with no success. (I guess this may be more of a logical problem than a programming one.) I have tried rewriting using && or ||, which have sequence points, but since these are short-circuiting as well, I haven't found a correct expression yet.

bers
  • 4,817
  • 2
  • 40
  • 59
  • 1
    Can you redesign it so that instead of `bool seen = false;` you have `int numberOfTimesSeen = 0;`? `assert(numberOfTimesSeen < 2)` seems much clearer and in line with what you want. – Nathan Pierson Feb 01 '23 at 18:33
  • Worth reading the answer to [Using bitwise operators for Booleans in C++](https://stackoverflow.com/q/24542/3422102) - you are fine, but ... – David C. Rankin Feb 01 '23 at 18:35
  • Perhaps a job for the [comma operator](https://en.cppreference.com/w/cpp/language/operator_other)? – jtbandes Feb 01 '23 at 18:36
  • `seen & (seen |= is_here)` is indeed UB. – Jarod42 Feb 01 '23 at 18:42
  • `assert([&](){bool res = (seen & is_here); seen |= is_here; return res;}());`? – Jarod42 Feb 01 '23 at 18:46
  • @NathanPierson yes, `int`s might work. I think one reason why they would work is because `int`s have a post-increment operator; if one could still use `bool++`, I would expect `assert(!(is_here ? seen++ : seen));` to work... – bers Feb 01 '23 at 18:47
  • @jtbandes how would I use this? I think the comma operator would allow me to have a pre-increment of the saved state, but not a post-increment. – bers Feb 01 '23 at 18:49
  • @Jarod42 `return !res` - otherwise, this looks good! – bers Feb 01 '23 at 18:50

1 Answers1

0

Through some trial and error, I have been able to come up with assert(seen ? false : true | (seen |= is_here)), the point being that ? introduces a sequence point separating the two evaluations of seen. In addition, true | ... sets the value of the latter branch without short-circuiting.

#include <iostream>
#include <assert.h>

int main() {
    bool seen = false;
    bool is_here;

    is_here = false;
    // pass
    assert(seen ? false : true | (seen |= is_here));
    assert(seen ? false : true | (seen |= is_here));

    is_here = true;
    // passes
    assert(seen ? false : true | (seen |= is_here));
    // after that, this passes
    assert(!(seen ? false : true | (seen |= is_here)));

    is_here = false;
    // same as this
    assert(!(seen ? false : true | (seen |= is_here)));

    std::cout << "Done.";

    return 0;
}
bers
  • 4,817
  • 2
  • 40
  • 59