Boost logo

Boost :

Subject: Re: [boost] [system] Would it be possible to trial a breaking change to Boost.System and see what happens?
From: Gavin Lambert (gavinl_at_[hidden])
Date: 2018-01-15 22:39:58


On 16/01/2018 03:53, Peter Dimov wrote:
> This is circular reasoning. My specification would return success when
> the function succeeds. How would it return success is determined by what
> is the established way to return success. The current de-facto standard
> way of returning success is zero, so I'd specify my function to return
> that. Were the standard way to signal success something else, my
> specification would reflect that something else.

Actually there are several competing "standards" for return values in
general.

Functions that can only ever return success or failure tend to return 1
for success and 0 for failure (as booleans; literally these values in
the case of C APIs but preferably as bool-typed values in C++).

Functions that can return one kind of success and several kinds of
failure tend to standardise on 0 == success, anything else == failure.
(Usually inspired by shell conventions.) Occasionally this convention
is abused by having some of the kinds of failure actually a qualified
success, for things like "you already own that mutex" or "the file you
were trying to delete didn't actually exist".

Functions that are expected to return several kinds of success as well
as several kinds of failure tend towards having >0 as success, <0 as
failure, with 0 as an in-between value that could be either depending on
context. Although implementations vary on whether the return value is
actually signed, with -1 the first error code, or whether it's
sign-magnitude, with 0x80000001 the first error code. Sometimes other
high bits encode other special conditions like warnings or an error
category.

Outcome resolves the latter case by returning a success-or-error type
that separately encodes each state, so that error_code itself doesn't
have to worry about success values -- but even in that case it might
still need to represent recoverable errors or partial-success warnings
where there wasn't any success result to return instead.

APIs written in the first style generally seem more "natural" (though as
always that depends on what you're used to), but become problematic to
change to the second style if they need to return multiple error states
in the future (since the success convention is flipped). Most commonly
this is done by punting and retaining the same return format but then
making error information available out-of-band via a thread-local
get-last-error call or similar.

The whole 0==success argument goes away if the operator bool does call
the error_category and asks it if the code is equivalent to {0,
generic_category()}. (Related: why does this not have a std::errc enum?)

It also begs the question of whether error_code() should be changed to
be {0, generic_category()} rather than {0, system_category()}, as this
would more unambiguously represent success. Of course, even that change
would probably break user code, unless more things did error_condition
compares.

> These two ways to phrase it are the same; generic success and generic
> failure to succeed are complementary. Today, the logic is expressed in
> terms of if(ec) and if(!ec); whether the function tests for failure
>
>    do_f1( ec );
>    if( ec ) return;
>
>    do_f2( ec );

The problem with this code is that if it is ignorant of the underlying
implementation (and only has a zero-success convention) it can *only*
test for complete success or not. There's no way to generically handle
partial success (or a recoverable failure), unless you can rely on the
error_condition mapping, which in turn relies on the underlying library
happening to implement that in a way that's compatible with your
expectations. (Woe betide you if you want to switch from library A to
library B but one of them forgot to implement a mapping, or elected to
map to different codes.)

And these things are important -- the primary advantage of error_codes
over exceptions is exactly for handling the recoverable cases without
invoking the exception handling machinery. Exceptions are invariably
superior for handling unrecoverable errors.

If you do remap the errors to your own domain as soon as possible then
that simplifies things as only the code directly adjacent to the
external library needs to worry about which error codes to track, and
anything higher up only needs to deal with your own error codes and your
error condition mappings. But this inevitably leads to a loss of
information about the original error cause if the error does end up
bubbling up outside the library (or otherwise reaching a generic logging
function).


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk