I
ImpalerCore
My project is still governed by a 1997 agreement with our client which
requires us to write code which "conforms" to C90 plus the two Technical
Corrigenda. Technically, that's not exactly a restrictive requirement -
"War and Peace" qualifies as conforming C code - but I try to follow the
spirit of the requirement, even if it was incorrectly worded.
I long ago adopted a policy of avoiding defining variables whose sole
job is to keep track of whether or not a condition is true; I prefer
re-stating the condition - it often leads to clearer code, and generally
does the right thing when the condition involves variables whose values
have changed since the last time it was evaluated. If the condition does
not actually need to be re-evaluated, most modern compilers can be
counted on to detect that fact (more reliably than I can), and use an
unnamed temporary for the same purpose that a named boolean variable
would have been used for. Therefore, I think this is a feature I won't
make much use of, even when I can do so.
While I think that is a valid rule of thumb, there are cases where
caching the result in a properly named variable can lead to more
readable and better performing code. In longer pieces of code, like
when parsing format strings, using booleans to represent state
conditions detected when traversing the string can be much easier to
read than repeating terse conditions. For example, if I need to
detect a leading zero, I'm perfectly happy to use a 'bool' variable to
name and track the condition.
\pseudocode
bool is_valid_format_X( const char* s )
{
bool leading_zero = false;
while ( ... )
{
...
if ( *p == '0' ) {
leading_zero = true;
}
...
if ( leading_zero ) {
...
}
++p;
}
...
if ( leading_zero ) {
...
}
return true;
}
\endcode
But the real advantage of defining 'bool' in C90 is that it is a
superior type name for 'int' when defining function interfaces that
take boolean parameters or that have boolean return values.
Here is an example that identifies a valid gregorian date from my date
library.
\code
bool c_is_valid_greg_date( struct c_greg_date date )
{
return ( ( date.year == INT16_MAX ) &&
( date.month == INT8_MAX ) && ( date.day == INT8_MAX ) )||
( ( date.year == INT16_MIN ) &&
( date.month == INT8_MIN ) && ( date.day == INT8_MIN ) )||
( ( date.year >= C_GREGORIAN_EPOCH_YEAR ) &&
( date.month >= C_JANUARY ) &&
( date.month <= C_DECEMBER ) &&
( date.day > 0 ) &&
( date.day <= gc_days_in_month_table[LEAP_INDEX(date.year)]
[date.month - 1] ) );
}
\endcode
If the 'bool' type is replaced with 'int', as in 'int
c_is_valid_greg_date( struct c_greg_date date )', now I need to
consider the possibility of whether the range of values is something
other than '1', or '0'. Functionally it would still operate as
intended. But from a cognitive perspective, 'bool' implies a narrower
semantic meaning to the return type than 'int' does. Do you use 'int'
to represent all your boolean parameters and return types? I would
find it more cognitively difficult to separate 'int' parameters that
are truly integers, and those that are booleans in disguise.
\code
/*!
* \brief Search a \c c_list for an object that satisfies the
* property evaluated by the predicate function \c pred_fn.
* \param list A \c c_list.
* \param property A property related to a list object.
* \param pred_fn The predicate function.
* \return The list node of the first matching object, or \c NULL if
* not found.
*/
struct c_list* c_list_search( struct c_list* list,
const void* property,
bool (*pred_fn)( const void*, const
void* ) );
/*!
* \brief Insert a new object into the \c c_list in sorted order.
* \param list A \c c_list.
* \param object The object.
* \param cmp_fn The comparison function.
* \return The head of the \c c_list.
*/
struct c_list*
c_list_insert_sorted( struct c_list* list,
void* object,
int (*cmp_fn)( const void*, const void* ) );
\endcode
In this example, 'bool' more clearly indicates the kind of function I
expect when searching a list verses sorting a list. For example,
searching a list of strings for strings that match a file suffix does
not require the same return type that a lexicographical comparison
would need. The comparison function mandates an 'int' return type for
to sort a list in alphabetic order, the predicate function for
searching the list does not. The 'bool' typedef clearly identifies
the return type expected to define your own search function to use
these list interface functions.
In the scenario of converting a floating point to bool using
'(bool)0.5', you can implement coding standards that prohibit explicit
or implicit conversions of floating point values to bool, which I tend
to avoid in general. The only expressions I use in 'if' statements
with implied equality are booleans and pointers. If I want to check
whether a floating point value is equal to zero, I make a point to
make that comparison explicit '== 0.'.
However, if I ever do use _Bool, implicit conversions from pointer
values to _Bool stand a very good chance of being involved. I often
write if(p) rather than if(p!=NULL), which is essentially the same idea..
I guess I don't see the issue if you avoid 'if ( (bool)p )' in your
expressions, and I think the only issue would be for systems where
NULL is not equivalent to 'false' (please correct me if I'm missing
something). And a typedef of 'enum { false, true }' to 'bool' doesn't
change the semantic meaning of either 'if (p)' or 'if (p != NULL)' in
C90 or C99 unless you do something like 'if ( (bool)p )', which is not
idiomatic C in either standards.
Feel free not to use it, but I find it extremely valuable in defining
semantics for function interfaces. Would I argue for it if I was
working on your project? Probably as I think the cognitive benefits
outweigh the risks of what you'd write in idiomatic C code. And I've
found the typedef to be compatible in C90 environments with judicious
use.
Best regards,
John D.