Your own error condition

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:

  1. User sent us an illogical request.
  2. There is some problem with the system which the user will not understand but which prevents us from returning the requested answer.
  3. 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 on error_codes, 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_conditions and error_codes.

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.

This entry was posted in programming and tagged , . Bookmark the permalink.

25 Responses to Your own error condition

  1. Vinnie Falco says:

    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.

  2. Artem Tokmakov says:

    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)

      • nobody says:

        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.

  3. Artem Tokmakov says:

    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.

  4. Marco Arena says:

    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 of variant or you just need an exception hierarchy, or perhaps boost::exception, which is able to store arbitrary amount of arbitrary type of information.

  5. nobody says:

    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).

  6. dbjsystems says:

    “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++.

  7. metablaster says:

    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:

      // C++ 17
      auto [ value, status ] = myfun() ;
      if (! value )  {
        // error condition 
      }
      

      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 …

  8. /*
    abstract generic structure, with no apparent solution domain
    */
    template <typename T1_, typename T2_>
    using pair_of_options = std::pair<optional<T1_>, optional<T2_>>;
    
    • 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/.

      • If anyone has any better idea I am more than willing to listen.

        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:

        // function declaration:
        // it either returns an `int` or fails
        result<int> myfun();
        
        //usage:
        result<int> yourfun()
        {
          BOOST_OUTCOME_TRY(value, myfun());
          ++value; // value is the successfully returned `int`
          return value;
        }
        

        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:

          /* abstract generic structure, with no apparent solution domain */
          template <typename T1_, typename T2_>
          using pair_of_options = std::pair<optional<T1_>, optional<T2_>>;
          /* suddenly here is the solution domain for the above */
          template<typename T>
          using result = pair_of_options<T> , optional< json_encoded_message > > ;
          

          The macro is avoidable me thinks.

          result<int> my_fun () ;
          // calling site
          result<int your_fun () {
          auto  [ V, S ] = my_ fun () ;
          if ( ! V ) return { { }, S } ;
          }
          

          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.

    • 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.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.