Re: When int meets bool over ==
On Mar 2, 4:18 pm, Gennaro Prota <clcppm-pos...@this.is.invalid>
wrote:
On Thu, 1 Mar 2007 07:08:12 CST, James Kanze wrote:
On Mar 1, 8:57 am, Gennaro Prota <clcppm-pos...@this.is.invalid>
wrote:
On Sun, 25 Feb 2007 06:31:55 CST, Alf P. Steinbach wrote:
[...]
Second, in order to communicate an error code, a function that
returns a
logically boolean success/failure often has logical false denoting
success, and logical true denoting failure.
In such cases I use to introduce a named constant, e.g.:
const bool failure( true );
if( f() != failure )
...
An even better solution is to modify the function to return an
enum:
enum ErrorStatus { failure, success } ;
Failure (or not) is neither true or false, so using a bool for
it is poor engineering.
Undoubtedly. But often you have no option to modify the function
(though one is always tempted :-)).
But it would be a better solution if you could:-).
Historically, I know, there are an awful lot of API's that do
use bool, or even an int which can only take two values here.
Posix has more than a few, with, generally, 0 indicating
success, and -1 failure. The Windows API, from what I've seen,
tends to use 0 for failure. So that:
if ( someSystemFuntion() ) {
// error under Posix, success under Windows
}
Off hand, I'd say that I've just made my point:-).
I've not done it yet (I've no extended developments under
Windows, and I only use the system API in the very lowest levels
anyway), but this suggests some sort of more general function,
succeeded, which is defined differently on the two systems.
Similarly, to find out why there was an error, you read errno
under Posix, call GetLastError() under Windows.
Note, too, that we are discussing the general case of a function which
has several possible return values, *one* of which means failure (or
not).
Which is even trickier. One universal convention: if the
function returns a pointer, a null pointer indicates an error.
And one fairly widespread convention: if the function normally
returns a small, non-negative integer, it is declared as
returning int, with -1 as an error indicator. Of course, this
results in:
if ( someFunction() ) {
// success for pointer, error for int
}
IMHO, however, this is less of a problem, since you wouldn't
normally write this anyway; you'd use the function to initialize
a variable of the correct type (since in case of success, you
want the value), and then write the test. At that point, since
you're testing a value of a known, non-boolean type, only
obfuscation would lead you to omit the == (or !=, or---a
frequent convention in the case of int---<).
Of course, the standard sort of encourages this sort of
obfuscation, by allowing things like:
if ( char const* p = getSomething() ) {
// got it...
}
And equally of course, this doesn't work for the case with int
values. (In practice, Posix open will never return 0, unless
you've closed standard in, for some reason.)
Which leads us back to my quote from Walter Scott: "Oh what a
tangled wed we weave, when first we practice to deceive". If
it's not a bool, don't treat it as one.
I shouldn't have snipped Alf's sentence "As an example, standard
'main' works this way" (though you can't call main() in C++), and my
example would have probably been better with an int-returning function
const int success( 0 );
...
You can still use an enum where I use the constant, if you like (the
enumerator initializer can become clumsy when you need something like
static_cast< time_t>( -1 ), though --see below) but in most, if not
all, of the cases you need one enumerator, so I'm not sure what does
that buy you.
Sentinal values are tricky. But normally, you'd write the test
out in full, and be done with it:
time_t t = time( NULL ) ;
if ( t != (time_t)( -1 ) ) {
// OK, I have the time...
}
(The function time may be a bad choice as an example. None of
the code I write has to be portable to a system where the time
isn't available, and I generally neglect testing the return
value completely.)
My own policy with regards to sentinal values is:
-- Null pointers are fine. The conventions are so well
established that I can't imagine anyone not being familiar
with them. (Of course, you still write "if ( p != NULL )"
to test it. Conventions or no, "say what you mean, and mean
what you say".)
-- I'll also accept the use of -1 in certain cases, especially
established cases like time_t, or file descriptors under
Posix (although the Windows API use of a manifest constant,
INVALID_HANDLE_VALUE, is even better here).
-- Other than that, I use the fallible pattern, which I
initially saw in Barton and Nackman.
There are many functions in the C library which use a similar
approach: time(), for instance; for it I usually write something like:
const time_t failure( -1 );
time_t t;
if( time( & t ) != failure )
...
Parts of the C library come from the same sources as Posix:-).
In practice, I've found the style above (when it's applicable) simple
and with a good level of readability but, as always, if there's
something better I'm all ears :-)
Well, the one problem I see is the number of failures:
time_t const failure( -1 ) ; // For time.
int const failure( -1 ) ; // For open.
And of course, if you also have functions which return 0 for
failure, you're in trouble.
My own solution (to date, at least) has been to do whatever the
API tells me, and to wrap, so that such situations only occur at
the very lowest level (written, hopefully, by someone who knows
the API).
--
James Kanze (Gabi Software) email: james.kanze@gmail.com
Conseils en informatique orient?e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S?mard, 78210 St.-Cyr-l'?cole, France, +33 (0)1 30 23 00 34
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]