2

Is it safe to loop over Objective-C BOOLs like so:

for (BOOL flagA = NO; flagA <= YES; flagA++)
    for (BOOL flagB = NO; flagB <= flagA; flagB++)
        // ...

I'd like to use this to cycle through all relevant permutations of flags in a XCTestCase.

But it appears as if YES++ is still YES at least on some platforms (hence leading to an infinite loop e.g. on iPhone 6 Plus simulator) whereas I would have expected that BOOL would just be treated as int (and hence YES++ becoming 2).

Do I have to loop over ints (my best guess) instead, or can the use of BOOL be salvaged in some convenient way?

Drux
  • 11,992
  • 13
  • 66
  • 116
  • 1
    Why don't you just use ints and then use integers as bools? – Sulthan Feb 23 '15 at 09:34
  • @Sulthan See last line in Q. So is this the way to go? Do you know why `BOOL` (apparently) works differently (in terms of `++`) on different platforms? – Drux Feb 23 '15 at 09:35
  • I can't currently see a mistake in your code because `YES` should be 1 and `NO` should be 0 and `BOOL` should be signed char but you should never compare booleans with `<=` or incrementing booleans. Those operations are something undefined. – Sulthan Feb 23 '15 at 09:39
  • The code in question actually works on my machine. – Sulthan Feb 23 '15 at 09:46
  • I'm confused about `YES++`. Does `1++` equal `2`, or is it only valid for variables? – Nicolas Miari Feb 23 '15 at 09:49
  • @Sulthan On an iPhone 6 Plus simulator? Here it does not (Xcode running on MacBook Pro.) – Drux Feb 23 '15 at 09:51
  • 1
    @NicolasMiari `YES++` was just meant as shortcut for `flag = YES; flag++`. – Drux Feb 23 '15 at 09:53

4 Answers4

4

You are all missing the point here. Drux is asking why can't he increment over BOOL, while it should be a char (8 bit value), which is perfectly incrementable.

The Answer is very easy. BOOL is sometimes a char and sometimes a bool depending on the target. From objc.h file:

#if !defined(OBJC_HIDE_64) && TARGET_OS_IPHONE && __LP64__
typedef bool BOOL;
#else
typedef signed char BOOL; 

If you iterate over a bool you will get value of 1 maximum.

EDIT:

Can you please add a reference to where the semantics of ++ for bool are specified? - Drux

Even though that bool has to be 8 bits minimum, it can't have any other value than 0 or 1. Why ? Because bool a = 3 (bool equal operator) converts 3 into a bool value, which is true which is 1.

So bool a = true; a++ is the same as bool a = 2; which makes a have a value of 1

michal.ciurus
  • 3,616
  • 17
  • 32
  • We weren't missing a point. We told him that what he's doing is not defined. – Sulthan Feb 23 '15 at 09:50
  • 1
    But you didn't tell him why. – michal.ciurus Feb 23 '15 at 09:51
  • The reason is not the actual macro implementation, the reason is the logic behind it. It's a boolean, so there is no increment defined. It's a boolean, so there is no comparison defined. – Sulthan Feb 23 '15 at 09:52
  • So, `bool` (**not** `BOOL`) is defined as a special, 1-bit type? And that makes it "wrap around" modulo 2? Didn't know that! – Nicolas Miari Feb 23 '15 at 09:52
  • I think you nailed it. Can you please add a reference to where the semantics of `++` for `bool` are specified? – Drux Feb 23 '15 at 09:54
  • @NicolasMiari I wish it was that easy. bool can't be 1 bit, because you can't create a pointer to 1bit. http://stackoverflow.com/questions/2064550/c-why-bool-is-8-bits-long – michal.ciurus Feb 23 '15 at 10:01
  • I see... but it still can be equal to 1 maximum. I wonder what under-the-hood, implementation-dependant mechanisms go into guaranteeing that. – Nicolas Miari Feb 23 '15 at 10:06
  • @Sulthan: Incrementing a bool _is_ defined. It changes 0 to 1, and it leaves 1 unchanged. – gnasher729 Feb 23 '15 at 10:11
0

I think @Sulthan means something like this (made overly explicit on purpose):

for(int indexA = 0; indexA <= 1; indexA++){
    for(int indexB = 0; indexB <= indexA; indexB++){

        BOOL flagA = (indexA == 1) ? YES : NO;
        BOOL flagB = (indexB == 1) ? YES : NO;

        // Use your flags (booleans) here...

    }
}

(Of course, you can use just the ints in place of booleans in Objective-C, if you want to avoid using too many redundant variables).

ADDENDUM: I actually performed a "Jump to definition" in Xcode (OSX project), and the part looks like this:

#if __has_feature(objc_bool)
#define YES __objc_yes
#define NO  __objc_no
#else
#define YES ((BOOL)1)
#define NO  ((BOOL)0)
#endif

(usr/include/objc/objc.h)

Can't "Jump to Definition" on __objc_yes (gives "Symbol Not Found")

Nicolas Miari
  • 16,006
  • 8
  • 81
  • 189
  • But why is sg. like this even necessary? Isn't `BOOL` just mapped to some type (`unsigned char` perhaps) with more than 1 bit in representation? – Drux Feb 23 '15 at 09:41
  • At the very least, I would avoid using booleans as loop variables because it will confuse a bit anyone who has to maintain your code down the road (it isn't something people are used to seeing). – Nicolas Miari Feb 23 '15 at 09:41
  • I know what you mean, but I think that is implementation-dependent and somewhat not guaranteed. If I remeber correctly, `true` is only required to be "non-zero", not necessarily 1 (I could be wrong). – Nicolas Miari Feb 23 '15 at 09:42
  • `__objc_yes` is a compiler literal, see LLVM documentation for it. It gets implicitly translated to `((BOOL)1)` too. – Sulthan Feb 23 '15 at 09:49
  • OK, so it **should** work then. Still, I stand by my opinion that it is bad practice, and he is better off using explicit `int`s as loop variables. – Nicolas Miari Feb 23 '15 at 09:50
0

The only way I see would be adding a break in your loop to escape the infinite loop.

Another possibilities is to use simple integer and stop the for loop when counter == 2

for (BOOL flagA = NO; YES; flagA++) {
    for (BOOL flagB = NO; YES; flagB++) {
        // Do something
        if (flagB)
          break;
    }
    if (flagA)
       break;
}
KIDdAe
  • 2,714
  • 2
  • 22
  • 29
  • Yep that would work even with `BOOL`s. (Although I would prefer looping over `int`s in this case -- in which case the breaks are IMO redundant.) – Drux Feb 23 '15 at 09:45
  • @Drux Yep I probably would too. But since the question was to find a way to use `BOOL` I just answered with the only possibility I saw. – KIDdAe Feb 23 '15 at 09:46
  • Thx, it's appreciated. – Drux Feb 23 '15 at 09:47
0

If you're set on operating over BOOLs, then instead of:

for (BOOL flagA = NO; flagA <= YES; flagA++)
    for (BOOL flagB = NO; flagB <= flagA; flagB++)
        // ...

You should really be doing something this (though it is not what you want):

for (BOOL flagA = NO; flagA != YES; flagA = !flagA)
    for (BOOL flagB = NO; flagB != flagA; flagB = !flagB)
        // This is the only safe way to 'iterate' BOOLs

The behaviour, (BOOL)++ is not well-defined* as a BOOL can only be YES or NO. What you really should be doing is casting your BOOL to an int, and iterating over that, or refactoring your loop entirely to use int types.

The problem with casting your BOOL values to ints is, as you have pointed out, BOOL is typedef'd to something with only 8 bits of information*, therefore it only makes sense to have 255 iterations. In fact in more recent times, BOOL is not cast-able at all because it is defined as a compiler intrinsic (objc_bool, which can have values __objc_yes and __objc_no). __objc_no++ has no meaning.

TL;DR My (strong) suggestion would be to refactor your code so you are iterating over integers, and inspecting BOOLs within each iteration. Whether you cast your BOOL values, or refactor your loop is up to you, but iterating over BOOL values in the way you have indicated is both unsafe and (now, because of that) unsupported.


* In past years, the implementation details of BOOL were obvious (namely a cast to an unsigned char). With the advent of compiler intrinsics, the details are hidden (though they are likely the same). The reason they are now hidden is because you're really not supposed to rely on them, and the easiest way to stop people relying on them is to hide them from the compiler altogether.

Ephemera
  • 8,672
  • 8
  • 44
  • 84
  • Why do you think the 2nd version is not what I want? It looks fine to me. (Although I agree hat looping over `int`s is the better option). – Drux Feb 23 '15 at 09:57
  • It seems that with this loop you won't even enter it once. flagA == flagB at first iteration of the second loop, then flagA == YES and you won't enter the first loop again. – KIDdAe Feb 23 '15 at 09:59