Does std::error_code's bool operator do the right thing?

848 views
Skip to first unread message

Paul "TBBle" Hampson

unread,
Jul 22, 2017, 7:42:34 AM7/22/17
to ISO C++ Standard - Discussion
A colleague mentioned that a problem he sees with std::error_code is that you have to use 0 for success, which surprised me.

I couldn't see anything in the standard (looking at [syserr]) that specifies this, except by intuition from the definition of error_code::operator bool().

Assuming that the default constructor and clear() does leave both error_code and error_condition in the "no error" state, it seems slightly surprising that error_code::operator bool() doesn't check something like

*this == error_condition{};

Which would be unambiguously "no error" or "unset", irrespective of this->error_category(). It might be surprising from a performance perspective, hopefully compilers would reduce that down to only slightly worse than the current check for 0, and potentially optimising for system_category would bring the common-case back to unquestionably efficient.

The counter-example that got me thinking about this is the HTTP example from what appears to be the most-cited write-up for using these types, the blog post series "System error support in C++0x", along with a note in a more recent blog post (by a different author) "Your own error code", which was linked from the isocpp.org front page recently.

Specifically, the latter blog post contains:

But our enumeration must meet one condition: numeric value 0 must not represent an error situation. 0 represents a success in any error domain (category). This expectation is later exploited when we are inspecting an std::error_code object:
1
2
3
4
5
6
7
void inspect(std::error_code ec)
{
  if (ec) // equivalent to: ec.value() != 0
    handle_failure(ec);
  else
    handle_success();
}
In this sense the mentioned blog post has it incorrect that numeric value 200 indicates success.


This seems in conflict with the design intention expressed by error_code, that it contain the unadulterated error number from the external or low-level system, e.g., [syserr.errcode.overview]

The class error_­code describes an object used to hold error code values, such as those originating from the operating system or other low-level application program interfaces.

Since HTTP specifies that any result code from 200 to 299 indicates "success", it would seem reasonable at first-glance that an error_code containing something in that range would compare equal to std::error_condition{} if that was really all you cared about.

However, in that case, the boolean value as it stands now would be 'false', and the code-sample I have included would do the wrong thing.

This seems like an easy mistake to make, given the intuitive interpretation of if (error_code) as "if an error occurred".

Conversely, I can see a good argument that in an error_condition, 0 be always taken as "success", since by my understanding, we expect error_condition to isolate us from platform-specifics such as this.

It'd be nice if this was explicit in the standard itself, somehow, rather than relying on the same intuition that is being foiled above.

Nicol Bolas

unread,
Jul 22, 2017, 10:53:36 AM7/22/17
to ISO C++ Standard - Discussion


On Saturday, July 22, 2017 at 7:42:34 AM UTC-4, Paul "TBBle" Hampson wrote:
A colleague mentioned that a problem he sees with std::error_code is that you have to use 0 for success, which surprised me.

I couldn't see anything in the standard (looking at [syserr]) that specifies this, except by intuition from the definition of error_code::operator bool().

...


It'd be nice if this was explicit in the standard itself, somehow, rather than relying on the same intuition that is being foiled above.

That's as explicit as the standard gets for these kinds of things. The standard is a technical standard, not a teaching book, after all.

Paul "TBBle" Hampson

unread,
Jul 22, 2017, 12:24:22 PM7/22/17
to ISO C++ Standard - Discussion
To be clear, the comment about explicitness was only a side-thought relating to the preceding line, about std::error_condition.

My actual question remains: Does std::error_code's bool operator do the right thing?

Chris Hallock

unread,
Jul 22, 2017, 2:48:44 PM7/22/17
to ISO C++ Standard - Discussion

Nicol Bolas

unread,
Jul 22, 2017, 4:31:57 PM7/22/17
to ISO C++ Standard - Discussion

I think by "right thing", he's asking whether doing `value() == 0` is the right test. Yes, that's how `std::error_code` is defined to work, but that means that you can't use a number of error code types with it. The OP mentioned HTTP errors, but I also bring up Vulkan errors. Negative error codes represent actual failure states, while non-zero positive ones represent "the function worked, but you need to know about how it 'worked' in this case" kinds of things (timeouts, etc).

Granted, it's not really reasonable to expect a simple number+category error code system to be able to handle all error code systems. At least, not without making it a template.

Chris Hallock

unread,
Jul 22, 2017, 8:18:48 PM7/22/17
to ISO C++ Standard - Discussion

Ah, I indeed misread Paul's message. I agree that error_code::operator bool() is problematic, since it could plausibly mean either "is nonzero" (as if it were an int) or "is a success code". The two interpretations are not equivalent if error categories are allowed to have success codes other than 0 and override equivalent() to indicate that. Assuming that's true, I'd argue that the second interpretation is more useful to users, but I can also imagine that the ambiguity between the two interpretations might be sufficient reason to just deprecate operator bool() and force users to be explicit by using ec.value() or ec == error_condition{}.

charleyb123 .

unread,
Jul 22, 2017, 8:48:50 PM7/22/17
to std-dis...@isocpp.org
On Saturday, July 22, 2017 at 7:42:34 AM UTC-4, Paul "TBBle" Hampson wrote:
It'd be nice if this was explicit in the standard itself, somehow, rather than relying on the same intuition that is being foiled above.
 
On Sunday, 23 July 2017 00:53:36 UTC+10, Nicol Bolas wrote:
That's as explicit as the standard gets for these kinds of things. The standard is a technical standard, not a teaching book, after all.
 
On Saturday, July 22, 2017 at 2:48:44 PM UTC-4, Chris Hallock wrote:
To be clear, the comment about explicitness was only a side-thought relating to the preceding line, about std::error_condition.

My actual question remains: Does std::error_code's bool operator do the right thing?

On Sat, Jul 22, 2017 at 2:31 PM, Nicol Bolas <jmck...@gmail.com> wrote: 
I think by "right thing", he's asking whether doing `value() == 0` is the right test. Yes, that's how `std::error_code` is defined to work, but that means that you can't use a number of error code types with it. The OP mentioned HTTP errors, but I also bring up Vulkan errors. Negative error codes represent actual failure states, while non-zero positive ones represent "the function worked, but you need to know about how it 'worked' in this case" kinds of things (timeouts, etc).

Granted, it's not really reasonable to expect a simple number+category error code system to be able to handle all error code systems. At least, not without making it a template.

The design *does* intend to handle all error code systems.  (We can debate if it is successful, but that is the intention for the current design.)

The mechanism is:

(1) peer-codes are defined in a domain (derived from 'std::error_category') 

(2) 'std::error_code' can statefully hold any code (each implicitly associated with its parent-domain).  The internal mechanism is type-erasure through domain derivation from std::error_category.

(3) Any domain author can implement an "equivalence-mapping" between a given domain, and any other domain "as appropriate".

The complexity comes in (3), as this is often a "many-to-many" mapping, and a design decision must be made regarding:

  *- Which domains are "aware" of what other domains, such that a value-mapping or equivalence-mapping can be performed?

In short, whose job is it to do the mapping?

A separate issue relates to the "magical sentinel value" of zero (0).

In the current design, it can only mean:

  *- zero (0):  "Nothing to see here, move along."

We might interpret that as either of:

  (a) Empty value (e.g., default ctor; or 'ec.clear()' was called)
  ...or...
  (b) No error

Mostly, and in trivial code, these always mean the same thing.  So, no problem, and no ambiguity.

For Best Practice, programmers should (typically) always test against an 'enum' (error-code/condition value).  That (properly and unambiguously) handles both cases above, and is *also* future-compatible to library evolution regarding code mapping to (future) new codes.

This is because:  An 'std::error_code' need not contain an error.  It contains meaningful platform-specific and instance-specific state for communication to the caller.  A library is perfectly allowed to define 'enum my_domain_codes' that enumerate many things, including failure, or success, or operation started successfully (but may fail later), or that a block operation is initiated that may eventually time-out, etc.

Thus, 'http_200' indicating 'success-and-everything-is-good' is a perfectly valid 'std::error_code' payload; and most importantly, this payload communicates a result suggesting that nothing is wrong.  Highlighting this best practice:

  ...
  ec = receive_http_response();
  if(ec)
  { // ohz noz!  freakout!    <===WRONG
    ...
  }

  ...
  ec = receive_http_response();
  if(ec != myhttp::RESP_200)
  { // ohz noz!  freakout!    <===OK
    ...
  }

The second case "works" (is "OK") because the library author *should* have implemented equivalence between zero '0' and 'RESP_200'.  So, if 'ec' is either of zero (0) or 'RESP_200', we do not perform conditional error-processing.

Implication would thus be something like:

(1) Empty 'std::error_code' can usually mean "no-error"

(2) Stateful 'std::error_code' can hold 'ok/fail/warn/info/anything' with instance-specific state upon which the caller can do deterministic conditional processing

(3) Stateful 'std::error_code' should be compared with enums to handle whatever scenario is encoded

(4) Library authors might know "magic numbers" for 'ec.value()', but users should typically never know what they are (they are irrelevant)

These are my opinion, and I concede 'std::error_code' is sufficiently new to industry practice that other viable opinions regarding its use are also reasonable.  A "heavy-handed" interpretation might be:

(a) Library authors can do whatever they want
(b) Users should _never_ call 'ec.value()'
(c) Users should (always?) compare 'ec' to 'mydomain::some_code', and (almost-) never use 'error_code::operator bool()'

Nicol Bolas

unread,
Jul 22, 2017, 10:04:59 PM7/22/17
to ISO C++ Standard - Discussion
On Saturday, July 22, 2017 at 8:48:50 PM UTC-4, charleyb123 wrote:
On Saturday, July 22, 2017 at 7:42:34 AM UTC-4, Paul "TBBle" Hampson wrote:
It'd be nice if this was explicit in the standard itself, somehow, rather than relying on the same intuition that is being foiled above.
 
On Sunday, 23 July 2017 00:53:36 UTC+10, Nicol Bolas wrote:
That's as explicit as the standard gets for these kinds of things. The standard is a technical standard, not a teaching book, after all.
 
On Saturday, July 22, 2017 at 2:48:44 PM UTC-4, Chris Hallock wrote:
To be clear, the comment about explicitness was only a side-thought relating to the preceding line, about std::error_condition.

My actual question remains: Does std::error_code's bool operator do the right thing?

On Sat, Jul 22, 2017 at 2:31 PM, Nicol Bolas <jmck...@gmail.com> wrote: 
I think by "right thing", he's asking whether doing `value() == 0` is the right test. Yes, that's how `std::error_code` is defined to work, but that means that you can't use a number of error code types with it. The OP mentioned HTTP errors, but I also bring up Vulkan errors. Negative error codes represent actual failure states, while non-zero positive ones represent "the function worked, but you need to know about how it 'worked' in this case" kinds of things (timeouts, etc).

Granted, it's not really reasonable to expect a simple number+category error code system to be able to handle all error code systems. At least, not without making it a template.

The design *does* intend to handle all error code systems.  (We can debate if it is successful, but that is the intention for the current design.)

I guess it depends on what you mean by "handle". It's a matter of what `error_code` is for.

If all you want is a way to pass a number + category around, then `error_code` certainly does that. Can you store a Vulkan error in such a type? Sure. Can you detect which Vulkan condition the type contains? Yes. Can you detect when an error code came from Vulkan or from something else? Yep.

But that's not really "handling" errors; that's just passing a typed number around. When handling an error, the most important question to ask, the one pretty much everyone asks first, is this:

> Did the operation which produced this error code succeed or fail?

`error_code` does not provide a way to detect that for all error code systems. And thus, I submit that it does not "handle" all error code systems in a meaningful way.

So, I would say that `error_code` and `error_condition` are not types that handle errors. They can transmit an error code from one location to another. But the user has no way of meaningfully answering this question, so they can't really help in that process.

Paul "TBBle" Hampson

unread,
Jul 23, 2017, 2:15:00 AM7/23/17
to ISO C++ Standard - Discussion

On Sunday, 23 July 2017 10:18:48 UTC+10, Chris Hallock wrote:

Ah, I indeed misread Paul's message. I agree that error_code::operator bool() is problematic, since it could plausibly mean either "is nonzero" (as if it were an int) or "is a success code". The two interpretations are not equivalent if error categories are allowed to have success codes other than 0 and override equivalent() to indicate that. Assuming that's true, I'd argue that the second interpretation is more useful to users, but I can also imagine that the ambiguity between the two interpretations might be sufficient reason to just deprecate operator bool() and force users to be explicit by using ec.value() or ec == error_condition{}.

This is roughly where I was going with this idea.

The 'assumption' there is the part I'm querying here, as I've seen 'teaching material' taking either side of the assumption. The standard doesn't specify either way, and the mismatch between error_code::clear() and error_code::operator bool() is (to my mind) a bet in either direction.

My current *feeling* is that error_code::operator bool() is somewhat like operator++(bool): While it mechanically makes sense for the underlying type-in-memory (integral types compare naturally to 0, and can be incremented), it expresses something inconsistent, confusing and problematic.

Paul "TBBle" Hampson

unread,
Jul 23, 2017, 2:35:35 AM7/23/17
to ISO C++ Standard - Discussion
My understanding of things like Chris Kohlhoff's original blog posts about them, is that error_code is only for transmitting errors around, and error_condition is only for doing generic comparisons with error_codes. Handling would be what you do based on the result of the series of both generic (error_condition) and specific (error_code) comparisons you perform based on your needs.

For me, this matches the list charleyb123 gave at the end of his post.

Looking at something like HTTP, as well as mapping some codes into generic_category, e.g., 404 => std::errc::no_such_file_or_directory, you could also implement a http_status_category as an error_condition enum which was simply 1,2, 3, 4, and 5, matching the first digit of the HTTP return code. Then you can do things like

if (ec == mylib::status_codes::http::category::redirect) { return redirect( request, ec ); }

where redirect knows how to deal with the specific HTTP redirect return codes.

Now I've written that, it seems like the same question *does* apply to error_condition. since in the HTTP example I just mooted, the 'natural' success code is 2. Or depending on your definition of success, 1, 2 and 3 are all 'success'.

likerai...@gmail.com

unread,
Sep 18, 2017, 2:23:12 PM9/18/17
to ISO C++ Standard - Discussion
I've been thinking around this matter lately, figuring out how to adapt some codebases to the new outcome library, and my short answer is "yes it does the right thing".

The long answer involves thinking about "std::error_code" as an status code where zero means nothing to report, then mind not every status report has to be fatal.  For "std::error_condition", zero should mean no map available. For supporting such non-fatal cases, what <system_error> seems missing is the concept of error severity. Maybe it all reduces to a naming issue: "std::error_category" to "std::error_domain", "std::error_code" to "std::error_status", "std::error_condition" to "std::error_category" (and "std::error_severity"?).

On my designs, I tend to start every error_code_enum or error_condition_enum as:
enum class error { none, /* ... */ };
This gives zero the importance it deserves, and it seems no specification says it should not be done. Then, overriding std::error_category::equivalent, a relationship is established to another enum exposing a common domain of severity conditions.

If community could argue standard subdivisions of a severity domain (wich seems the real problem here), a proposal for inclusion of a severity condition seems trivial. Adding another almost-clone of "std::error_code" called "std::error_severity" might seem proper but would confuse users even more due to the lack of restrictions in the specification. If actual design has to be embraced I'd opt-in for:

namespace std {
 
enum class errs { none, /* ... */ };
 
template<> struct is_error_condition_enum<errs> : true_type {};

 
class error_category {
 
public:
 
/* ... */

 
virtual error_condition default_error_condition(int val) const noexcept;
 
// Translates status to generic category
 
// LWG should restrict this to std::errc
 
// should default this to a missing "unknown" posix category
 
// luckily static_cast<std::errc>(0) is available

 
virtual error_condition default_error_severity(int val) const noexcept; // NEW
 
// Translates status to generic severity
 
// LWG should restrict this to std::errs
 
// should default this to std::errs::none

 
virtual bool equivalent(int code, const error_condition& cond) const noexcept;
 
// LWG should break dependency on default_error_condition here

 
};

 
const error_category& severity_category() noexcept;

 
class error_code {
 
public:
 
/* ... */
  error_condition default_error_condition
() const noexcept;
  error_condition default_error_severity
() const noexcept; // NEW
 
};

 
/* ... */
}

As a sidenote, if <system_error> was designed as a transportation only container then I see no point in the whole comparison mechanism. If it is there, I can only see it useful if used to define granularity on handling of errors across domains. Also, outcome library is focusing on returning either the result or the error, but not both result and status. Maybe the problem lies on domains with non-fatal errors being too heterogeneous for <system_error> one-fits-all approach to adapt them. If so, then I guess operator bool should be just deprecated along whole "std::error_condition", and "std::error_code" to "std::error_code" comparators be reworked to allow customization. On the actual design there is no proper separation of transportation and handling anyway (actually there is not even proper separation on interface and implementation due to lack of accurate documentation).

So my personal opinion, as I stated before, is to reserve zero on "std::error_code" as no status report, and to reserve zero on "std::error_condition" as no category mapping available. Of course that would hurt any error domain not using zero as OK, if there is still any of those around. Standard definitions with no implementation restrictions lead to portability problems like the one stated here. It just seems <system_error> was standarized too early, too inmature, not portable enough. Lets help make it evolve somehow.

Reply all
Reply to author
Forward
0 new messages