In the previous post we have seen how you can create your own error-code enumeration for representing different reasons for failure in your component, and how you can store them and convey them type erased via std::error_code
. In this post we will see how you can inspect an error situation encoded in std::error_code
, and how you can build useful queries about error conditions.
Let’s recall the task. In the previous post we have created a custom error code enum:
enum class FlightsErrc { // no 0 NonexistentLocations = 10, // requested airport doesn't exist DatesInThePast, // booking flight for yesterday InvertedDates, // returning before departure NoFlightsFound = 20, // did not find any combination ProtocolViolation = 30, // e.g., bad XML ConnectionError, // could not connect to server ResourceError, // service run short of resources Timeout, // did not respond in time };
And we have plugged it into the C++ mechanism for handling error codes, so that FlightsErrc
can be interpreted as std::error_code
:
std::error_code ec = FlightsErrc::InvertedDates;
This represents errors form the sub-system responsible for finding flight connections. We have another system for finding available seats on the previously found flights. It also can fail for a number of reasons:
enum class SeatsErrc { // no 0 InvalidRequest = 1, // e.g., bad XML CouldNotConnect, // could not connect to server InternalError, // service run short of resources NoResponse, // did not respond in time NonexistentClass, // requested class does not exist NoSeatAvailable, // all seats booked };
Suppose we have plugged it into the system in the similar way, by having defined a yet another private error category, so that this enum can also be interpreted as an error code:
std::error_code ec2 = SeatsErrc::NoResponse;
We could of course compare instances of std::error_code
against our enum values:
if (ec == FlightsErrc::InvertedDates) { /* do something */ } else if (ec == SeatsErrc::NoResponse) { /* do something else */ }
But this would usually be a wrong thing to do. What I want to do with an std::error_code
is either to log its numerical value (without any if statements), or to tell whether it falls into one of the following conditions:
- User sent us an illogical request.
- There is some problem with the system which the user will not understand but which prevents us from returning the requested answer.
- No airline we are aware of is able to offer a requested trip.
Regarding logging, we have already implemented it in the previous post. Regarding the classification, we can do it by implementing a custom error condition. This is quite similar to implementing a custom error code: we will define an enumeration and then teach the Standard Library how to use it as an std::error_condition
:
enum class FailureSource { // no 0 BadUserInput = 1, SystemError = 2, NoSolution = 3, }; namespace std { template <> struct is_error_condition_enum<FailureSource> : true_type {}; } std::error_condition make_error_condition(FailureSource e);
The difference from registering an error code is that we use type trait is_error_condition_enum
instead of is_error_code_enum
, and that we define a conversion to error_condition
rather than error_code
. with the above declarations in place, we can make the following compile (but not link yet):
std::error_condition cond = FailureSource::BadUserInput;
So, the library allows you to register your enumeration as either error_condition
or error_code
. The difference between the two is:
error_code
is used for storing and transmitting error codes as they were produced by originating library, unchanged;error_condition
is used for performing queries onerror_code
s, for the purpose of grouping or classification or translation.
And what about std::error_category
? Well, you can think of it as a technical detail for implementing custom error_condition
s and error_code
s.
You can test an error_code
against the desired condition by using equality comparisons:
std::error_code ec = FlightsErrc::InvertedDates; assert (ec == FailureSource::BadUserInput); assert (ec != FailureSource::SystemError);
This already compiles. Now we only have to define the semantics, so that comparison results are those indicated by the assertions. You may consider using operator==
somewhat of an ab-usage for this purpose, as it does not retain the properties of equivalence relations, but this is how checking for error conditions is defined in the Standard Library. Or it might be my unconventional usage of the facility to have a condition match to more than one error code value.
Defining semantics for conditions
In order to define the matching of error codes against conditions, we will have to define another category derived from std::error_category
, again, in a CPP file:
namespace { // anonymous namespace struct FailureSourceCategory: std::error_category { const char* name() const noexcept override; std::string message(int ev) const override; bool equivalent(const std::error_code& code, int condition) const noexcept override; }; }
The first two functions you are already familiar with from the previous post. The first gives a short name for our category, the second gives text names to the enumerated values:
const char* FailureSourceCategory::name() const noexcept { return "failure-source"; } std::string FailureSourceCategory::message(int ev) const { switch (static_cast<FailureSource>(ev)) { case FailureSource::BadUserInput: return "invalid user request"; case FailureSource::SystemError: return "internal error"; case FailureSource::NoSolution: return "no solution found for specified request"; default: return "(unrecognized condition)"; } }
The third function, equivalent
describes for each value of enum FailureSource
what values of std::error_code
it matches to:
bool FailureSourceCategory::equivalent( const std::error_code& ec, int cond) const noexcept { switch (static_cast<FailureSource>(cond)) { case FailureSource::BadUserInput: return /* TBD */; case FailureSource::SystemError: return /* TBD */; case FailureSource::NoSolution: return /* TBD */; default: return false; } }
Here, ec
represent the error code we are inspecting, and cond
is the numeric value of our condition enum. The philosophy here is that by default the category is not equivalent to an error_code
unless you explicitly indicate that it is. This has some practical implications, because it is not only this function that will be called to determine equivalence.
So, let’s try to define semantics for condition FailureSource::BadUserInput
. In case of FlightsErrc
we can see that the enumeration is not contiguous: it has already been prepared for categorization:
enum class FlightsErrc { // no 0 NonexistentLocations = 10, // requested airport doesn't exist DatesInThePast, // booking flight for yesterday InvertedDates, // returning before departure NoFlightsFound = 20, // did not find any combination ProtocolViolation = 30, // e.g., bad XML ConnectionError, // could not connect to server ResourceError, // service run short of resources Timeout, // did not respond in time };
- between 10 and 20: bad user input,
- between 20 and 30: no viable solution,
- above 30: internal system error.
In case of SeatsErrc
there is no such partitioning, so we have to enumerate all the interesting values. There is one: SeatsErrc::NonexistentClass
.
Our condition then can be written as:
case FailureSource::BadUserInput: // for SeatsErrc: if (ec == SeatsErrc::NonexistentClass) return true; // for FlightsErrc if (ec.category() == FlightsCat) return ec.value() >= 10 && ec.value() < 20; // for any other error_code enum: return false;
For FlightsErrc
values, in order to perform arithmetical expression we check whether the category of error_code
is that specified for FlightsErrc
; but as we know from the previous post, this category has been defined as a private detail, and is inaccessible to us. We did not have to make the category a private detail, and in fact other authors suggest using a factory function for returning the category object for your enum; for instance, see here. But there is still a way to access this category object indirectly: create an error_code
of which we are sure it stores a FlightsErrc
, and access its category:
const std::error_category& FlightsCat = std::error_code{FlightsErrc{}}.category();
operator==
on std::error_category
actually compares object addresses. Now that we have matched the category, we just need to check if the numeric value of the error is within the desired range.
Defining other cases is similar, and the entire equivalent
function looks like this:
bool FailureSourceCategory::equivalent( const std::error_code& ec, int cond) const noexcept { const std::error_category& FlightsCat = std::error_code{FlightsErrc{}}.category(); switch (static_cast<FailureSource>(cond)) { case FailureSource::BadUserInput: if (ec == SeatsErrc::NonexistentClass) return true; if (ec.category() == FlightsCat) return ec.value() >= 10 && ec.value() < 20; return false; case FailureSource::SystemError: if (ec == SeatsErrc::InvalidRequest || ec == SeatsErrc::CouldNotConnect || ec == SeatsErrc::InternalError || ec == SeatsErrc::NoResponse) return true; if (ec.category() == FlightsCat) return ec.value() >= 30 && ec.value() < 40; return false; case FailureSource::NoSolution: if (ec == SeatsErrc::NoSeatAvailable) return true; if (ec.category() == FlightsCat) return ec.value() >= 20 && ec.value() < 30; return false; default: return false; } }
Now, you might ask, why do we need to define an enumeration, and then these customizations, this error category, if we could have just written three functions: is_system_error()
, is_bad_input()
, is_no_solution()
. We could and this would work for enums FlightsErrc
and SeatsErrc
, but no other. Suppose all this is a library and someone else will be using it, they will create their own error code and will want it to be recognized by my condition. The C++ framework for error codes allows that: you can teach a new error condition to recognize existing error codes, but also you can teach a new error code to be recognized by existing error conditions. You can do it through a yet another virtual member function in std::error_category
:
bool equivalent(int code, const error_condition& cond) const noexcept;
It has the same name equivalent
but different set of parameters. So, the way operator==
between error codes and error conditions is implemented, it takes both overloads into consideration:
bool operator==(const error_code& lhs, const error_condition& rhs) noexcept { return lhs.category().equivalent(lhs.value(), rhs) || rhs.category().equivalent(lhs, rhs.value()); }
And that’s it. Now we can test our condition:
int main() { std::error_code ec = FlightsErrc::NoFlightsFound; assert (ec == FailureSource::NoSolution); assert (ec != FailureSource::BadUserInput); ec = SeatsErrc::NonexistentClass; assert (ec != FailureSource::NoSolution); assert (ec == FailureSource::BadUserInput); }
For a fully working example see here.
Wow, is this real? I have been working with error codes for a few years now and I had no idea. In fact I have on my TODO for Beast to make different categories for HTTP parsing errors and HTTP stream errors. So you’re telling me I can do this using categories instead? Brilliant
Hi Vinnie, and congratulations on getting Beast into Boost. My team has decided to move to Beast, after the verdict.
It was the primary motivation for starting this blog: that there is so much good stuff in C++ that people do not know about, because it is not advertised, and you can only sometimes learn it by chance.
This looks intersting! Any thoughts on dealing with error categories when they are used in dlls? Static objects and comparing addresses won’t work well in that scenario. See something like this: https://connect.microsoft.com/VisualStudio/feedback/details/1053790
Thanks for bringing this up. Maybe it deserves a separate post. This problem typically do not appear in Linux (unless you explicitly ask for it), it is specific to Windows DLLs, and boils down to the fact that globals may get duplicated in each dll. One typically resolves this problem by making sure the global is defined in one DLL (and no static library), whereas other DLLs and the main executable only reference it. The same problem is faced by Boost.Log library (they require one global place for loggers), and the way they solve it is the guidance in the documentation: “One thing should be noted, though. If your application consists of more than one module (e.g. an exe and one or several dll’s) that use Boost.Log, the library must be built as a shared object. If you have a single executable or a single module that works with Boost.Log, you may build the library as a static library.”
(see http://www.boost.org/doc/libs/1_64_0/libs/log/doc/html/log/installation/config.html)
This is not specific windows problem. You will get this problem also on linux. Using weak references could hide it a little, but do not prevent troubles if there are static objects with same name and different type declaration and are being passed across the border of shared libraries. IMHO it is C++ problem and a reason why most interfaces are in C.
Could you elaborate on this? Once I have discovered this problem with duplicated globals on Windows, I tried to reproduce it with Linux shared libraries, but my tests showed that globals do not get duplicated there. I always thought that this is what PIC is for.
I would really love Log library to not depend on global objects like that 🙂 Same goes for the error_category. Making sure everyone compiles their categories into dll, and making sure you only have at most one dll per category is, let’s put it this way, tricky )
Although, of course, likely there’s nothing else we can do about the issue at this point.
Thanks for the great posts. I am wondering if you mean to write another one about enriching error_code/error_condition with additional data (e.g. considering your example, suppose you get a ProtocolViolation and you want to provide additional information about the violation)?
There will be a sequel to these two posts, but not about additional information. If you really make use of additional information other than just
string
, you may end up in the situation where each different code requires different data, of arbitrary size. Either you will need to resort to some sort ofvariant
or you just need an exception hierarchy, or perhapsboost::exception
, which is able to store arbitrary amount of arbitrary type of information.Thanks for the clarification.
Looking forward to the next posts!
I would better use for common objects solution described here https://stackoverflow.com/questions/4616148/sharing-memory-between-modules, instead of addresses (and instead of PIC).
“unconventional usage of the facility” … good. but this is putting us all back to square one perhaps?
what would be the “conventional” usage of the facility? do we question the whole concept? design? or just implementation? as far as I can see (and I can) the key benefit is integrating the one’s code (library) with the std and others so that **usage** is “standard” or “normal” C++.
By “convention” I mean how the Standard Library uses this facility to map system_category onto a generic_category. It looks like 1-to-1 mapping, even though the former represents an abstract condition and the latter a concrete error code value. In this case the problem with operator== not having the property of equivalence does not occur.
I already use std error handling classes, so most of the stuff here is known to me, however I never understood how to make use of error_condition. I usually just stick with default_error_condition and now realize how limited my error handling code is!
The fact that you can assign an error category to error condition in addition to error code is new stuff for me.
although it looks like wrong usage because error category implements following method:
std::error_condition default_error_condition(int err_value) const noexcept
you should override it, which you didn’t in this tutorial, which in turn asks a question if assigning new error category for new error condidtion is valid.
it’s complicated as hell!
thanks a lot for this tutorial explained everything I was confused about.
As probably the latest clear report about the state of errors/returns management in C++, please see: https://herbsutter.com/2018/07/02/trip-report-summer-iso-c-standards-meeting-rapperswil/ …
Right now (2019 Q3) probably the only sane part of std:: for error handling is decades old concept in c++ form:
std::errc
, . Its usage of is the only part of system_error I am using, including the MSVC implementation of system_error category to obtain WIN32 codes and messages.Since I can not wait until C++23 and
std::error
, I have designed and I am using my own concept. Which is “shameful homage” to GO concept:If anyone has any better idea I am more than willing to listen.
ps: Thanks god, that report above also implies the proposals like std::outcome are not going to happen …
Can we see the return type of function
myFun()
in your example?Thanks. And sorry for the trouble with code snippets. Unfortunately, WordPress does not support the Markdown syntax. I keep the instructions here: https://akrzemi1.wordpress.com/about/.
While “better” depends on individual criteria, let me share my preference. If I did not like exceptions (which I do), I would go for Boost.Outcome. Assuming that the typical thing you want to do with the information about the error is to forward it up the call stack, you would use it like this:
It uses a macro, and for some people “no-macros” is a strong requirement, so they will not like it.
Herb Sutter calls this the library implementation of his proposal.
I have implemented a solution on top of that structure as the foundation of my value/status concept. Let me clarify the readme’s on the github and post back …
I have to say, I fail to see how does outcome fit into current state o affairs around C++20. It is simply “not there”? Herb is not mentioning it and Herb is very keen on P0709. Not because he is the author, but because that is a solution (at last) to a huge problem C++ has in its foundations for decades past.
Nial’s outcome I respect and know very well. Although I might think outcome is over engineered. With that “pair of optionals” I presented above, I think that might be implemented without complex coding:
The macro is avoidable me thinks.
And so on , that is the concept. Many thanks for reading.
Thanks for sharing your solution. The benefit I see is that it works well with C++’s structured binding. The drawback I see is that now, that you depart from using a discriminated union type, you have to assign some interpretation to the state where the returned result has both the optionals empty or both optionals non-empty.
Here is the working program with the working concept
https://wandbox.org/permlink/Wy7YIqFTgviV52kf
Yes that is the conscious and key conceptual decision value AND status, not value OR status. Further to that, I postulate:
1. if value AND status are empty that is an fatal error (std::exit() )
2. if value AND status are not empty that is info state, e.g in dealing with TCP retval’s
3. Just value –> OK state
4. Just status –> ERR state
I have developed a little top level traits templates for std::errc and for WIN32 ::GetLastError and then on top of that I have developed traits to use in my little SQLite3 C++ lib.
It loooks rather complex but just because of namespaces and long type names … And yes there are macros.