Drop the boolean data type, its broken. (or fix it)

Hi all, recently I have discovered some errors in the boolean data type. I have posted an issue to GitHub which also contains a fix, however for community awareness :stuck_out_tongue: I have started this thread to discuss the issue.

Also if anybody does not understand why the boolean type is unsafe to use, or just want a clearer explanation please feel free to ask.

The website and forum section does not have much action, so hopefully many people will have the chance to see this.
If a mod feels its better in the suggestions for the Arduino project forum, then feel free to move it.

First up, don't worry newbies, simply use bool instead of boolean in your code, problem solved.
The boolean alternative should only be used in C code.

The main problem stems from the fact, that integer conversions are unlike boolean conversions; and boolean is actually defined as an unsigned 8 bit integer ( uint8_t ), not a bool. Therefore it is subject to integer conversions which the standard describes follows ( only copied info for unsigned destination, which is what a boolean is ).

4.7 Integral conversions [conv.integral]
2 If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source
integer (modulo 2
n where n is the number of bits used to represent the unsigned type). [ Note: In a two’s
complement representation, this conversion is conceptual and there is no change in the bit pattern (if there
is no truncation). — end note ]

Where the boolean type conversion is as follows

4.12 Boolean conversions [conv.bool]
1 A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a
prvalue of type bool. A zero value, null pointer value, or null member pointer value is converted to false;
any other value is converted to true. A prvalue of type std::nullptr_t can be converted to a prvalue of
type bool; the resulting value is false.

This all breaks down as true and false are subject to integral promotion, and the standard says:

A prvalue of type bool can be converted to a prvalue of type int, with false becoming zero and true
becoming one.

So when we assign a 5 to a boolean, we'd expect it to convert to true, however the code below returns false.

true == boolean( 5 );

This is because the code is actually running the equivalent of this snippet ( after integral promotion ).

 uint8_t( 1 ) == boolean( 5 );

Which is clearly false.

All except one of these conditions should be true using boolean logic, your Arduino will beg to differ!

void setup() {
  Serial.begin(9600);
  Serial.println( true == (bool) 57 ? "true" : "false" );
  Serial.println( true == (boolean)57 ? "true" : "false" );
  Serial.println( true == bool( true ) ? "true" : "false" );
  Serial.println( bool( 1 | 2 | 4 | 8 ) == boolean( 1 | 2 | 4 | 8 ) ? "true" : "false" );
  Serial.println( true == false + 1 ? "true" : "false" );
  Serial.println( true + true == true ? "true" : "false" );
  Serial.println( bool( true + true ) == true ? "true" : "false" );
  Serial.println( boolean( true + true ) == true ? "true" : "false" );
}

void loop(){}

Also the actual Arduino reference makes claims which I have just proven otherwise.

"A boolean holds one of two values, true or false." and "Any integer which is non-zero is true ( in a boolean sense )."

As I have clearly shown that: "true != (boolean)57" their quotes above are only true for the real standard bool.

Their specific Arduino conventions truly are defining a new language. I also motion to remove the #defines for true and false ( at least for the C++ side of things ).

Note: the standard explicitly disallows #defining keywords:

17.4.3.1.1 Macro names [lib.macro.names]
... Nor shall such a translation unit define macros for names lexically identical to keywords.

I'm still working on a proof for why true and false defines should be dropped, however the statement above should seal the deal.

I have replied below with some code that is not using a boolean comparison, it should be good enough proof that the data type needs to be removed.

One way to make code safe when using boolean is to double negate the contents.

boolean b = !!57;

Now you can guarantee that b is either 0 or 1 as 57 has been through a real boolean conversion.

Here is one last example which may clearly show the lack of boolean conversion:

void setup() {                
  Serial.begin(9600);
  
  bool arr1[6];
  boolean arr2[6];
  
  arr1[0] = arr2[0] = 'H';
  arr1[1] = arr2[1] = 'e';
  arr1[2] = arr2[2] = 'l';
  arr1[3] = arr2[3] = 'l';
  arr1[4] = arr2[4] = 'o';
  arr1[5] = arr2[5] = 0;
  
  Serial.print( "Printing bool array: " );
  Serial.println( ( char* ) arr1 );
  Serial.print( "Printing boolean array: " );
  Serial.println( ( char* ) arr2 );
}
void loop(){}

Some of this text I have pieced together from other pages, hopefully its presented understandably. The GitHub issue has additional info, however ask away if questions are brewing. boolean data type causes logic errors. · Issue #2147 · arduino/Arduino · GitHub

In C, the rule was that you should never compare a Boolean to true.
"if ((Boolean)57) print("true");" works fine.

I've often wondered what the point of the boolean type was in the first place.

westfw:
In C, the rule was that you should never compare a Boolean to true.
"if ((Boolean)57) print("true");" works fine.

Thats because C does not have a boolean type, and therefore does not define any limitations to its contents, it only gives value to true and false which is why you should not compare. It is not suitable for C++, especially since bool is a real type and true is one of its values. C++ also ads implicit casts, conversions and promotions ( boolean logic ), where as C does not, but can have some explicit conversions like !! mentioned in my first post.

Also in your snippet, boolean(57) is converted to a real bool value as it is used in a boolean context, in the comparison true is promoted to int which integral promotion rules enforce, the RHS does not down cast to bool. There is an entirely different set of rules being applied.

Yeah, I was astonished when I saw it was an int. Just an artifact pushed into C++ Arduino so the core can use a 'boolean' in its C code ( wiring.c and others ).

Regarding the true and false defines, does this mean that even if I declare variables as bool, integer values are still used for true (1) and false (0), e.g.

bool foo = true;

is equivalent to

bool foo = 1;

?

That is what the code will look like after pre processing but before the compiler processes it.
The 1 is converted to a bool before assigning. The defines do not affect the compilers internal meaning of true/false.

What I propose to do is change Arduino.h to use the boolean functionality when compiling C code, but use the built-in method when in C++:

#ifdef __cplusplus
	typedef bool boolean;
#else
	typedef uint8_t boolean;
	#define true 0x1
	#define false 0x0	
#endif

bool/boolean is stored as 8 bits, yes?
So why not just use a byte, and set it to 0 or 1. That's all I ever do.
Then you can use all the bits for flags too:

if ((flag & b00000001) ==1){
// bit 0 is set
}
else {
// bit 0 is clear
}
// and same for other bits
if ((flag & 0b00000010) == 0b00000010){

if ((flag & 0b00000100) == 0b00000100){

if ((flag & 0b00001000) == 0b00001000){

etc.

Comes in really handy for reading comparing PORTs too.

Does the C++ spec mean that
if (Serial.available()) { // blah }
is now slower (in C++) than
if (Serial.available() > 0) { // blah }
because it first has to convert the paren contents to "bool" for the sake of "if", and then check for "true"? (As opposed to C, where it would just check for non-zero...
Or does the optimizer fix all that for us?

I'm not sure I understand the advantages of forcing bools to be only 0 or 1, aside from a sort of intellectual purity...

I figure the compiler will take of it.

Integral comparisons are done within the integer domain.

Consider ( true, not the define ):

true == true;

Both boolean values must be promoted to int before any comparison is done.
As with if( value ), *value'*s final result is converted into a boolean, then the comparison involves a promotion back to int.
Even though bool is an integral type, it has the lowest integer rank. And integer promotions only consider variants of int ( short/long/signed/unsigned ).

There is an awful lot of things happening in the background of C++ code.

It seems like C++ would produce more initial code ( than the C alternative you provided ) as the conversion to bool would change something on any value apart from 0. However the compiler can see this, and reduce it to its optimal form. Some C++ texts even recommend enforcing the conversion so its clear to other programmers you are using a boolean value ( value != 0 ), as its widely known that the optimization will be seen. In fact it was probably once a hard coded optimization as it is such a common C idiom, it was most likely expected to be littering C++ code everywhere.

5.10 Equality operators [expr.eq]
1 The == (equal to) and the != (not equal to) operators have the same semantic restrictions, conversions, and
result type as the relational operators except for their lower precedence and truth-value result.

And when we go there we can see:

5.9 Relational operators [expr.rel]
1 The operands shall have arithmetic, enumeration, or pointer type, or type std::nullptr_t. The operators < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to) all yield false or true. The type of the result is bool.
2 The usual arithmetic conversions are performed on operands of arithmetic or enumeration type

And msdn sums up integral promotion nicely ( which is what usual arithmetic conversions are when referring to integral types ).

Integral promotions are performed on the operands as follows:

  1. If either operand is of type unsigned long, the other operand is converted to type unsigned long.
  • If preceding condition not met, and if either operand is of type long and the other of type unsigned int, both operands are converted to type unsigned long.
  • If the preceding two conditions are not met, and if either operand is of type long, the other operand is converted to type long.
  • If the preceding three conditions are not met, and if either operand is of type unsigned int, the other operand is converted to type unsigned int.
  • If none of the preceding conditions are met, both operands are converted to type int.[/quote]

I'm not sure I understand the advantages of forcing bools to be only 0 or 1, aside from a sort of intellectual purity

I'll hopefully have a better explanation in the future, but:
It provides a sturdy platform for anything yes/no, as every value has meaning. Whereas only 2 values have meaning in the Arduino boolean, and 254 values are meaning-less.

It also allows us to make claims like the Arduno reference: Any integer which is non-zero is true as this is only made possible by boolean conversions ( number to bool ), and integral promotion rules for bool ( bool to int ). Without the rules while using the Arduino boolean; its up to you as a programmer to ensure that values are equal to one or zero before assigning to boolean, or to check its contents is within a certain range ( !=0, >0, <1, ==1 ). Which reflects the behaviour of C where the only boolean conversions you get are in a boolean context: if( value ), !!value, etc... And Arduino's boolean does not provide a boolean context.

I have now got a sturdy poof why true and false as defines cannot be used either. I just need to simplify it into a convincing argument.

I also have changed my fix to #define true !false for the C side of the code.

C and C++ agree on false==0. I think I'd prefer to see
#define true (!false)
If thats still acceptable.

westfw:
C and C++ agree on false==0. I think I'd prefer to see
#define true (!false)
If thats still acceptable.

Oops, that is actually what my code is on GitHub, I modified the example in my post above without reading the line ( I reordered mine so false is defined first, then inadvertently copied the second line from this threads example. )

I modified my post above, cheers.

I have added a pull request to the Arduino source.

For the time being, here is some code you can test. This shows why true and false cannot be defines to literal numbers.

void setup() {
  Serial.begin( 9600 );
  
  DoSpecific( true );
  DoSpecific( false );
  DoSpecific( 1 );
  DoSpecific( 0 );
}

void loop(){}

void DoSpecific( bool value ){
  Serial.print( "The boolean value is: " );
  Serial.println( value ? "TRUE" : "FALSE" );
}

void DoSpecific( int value ){
  Serial.print( "The integer value is: " );
  Serial.println( value, HEX );
}

Good news!

Today my GitHub issues were closed and this problem resolved. This fix is part of the core and will be available in version 1.5.9, and possibly 1.0.7.

Only took six months, but still happy to see it done.

pYro_65:
Today my GitHub issues were closed and this problem resolved.

Does this mean that bool and boolean now are synonymous in a sketch?

Manganus:
Does this mean that bool and boolean now are synonymous in a sketch?

Only when using C++ in version 1.6.0 or 1.0.6 and the not yet released future versions.
If you use a .c file (compile C code), then bool is an integer (unsigned char).