Plugging a library into boost::system::error_code - Boost.Outcome documentation

# Plugging a library into `boost::system::error_code`

See here for this guide, but for `std::error_code`.

This section illustrates how you can hook into the `boost::system::error_code` system from the Boost in order to work with your own set of error codes. As is usually the case in C++, doing this is straightforward but requires typing boilerplate to tell Boost.System about your custom error type. This is not part of Outcome library, but we still provide this short guide here, because how to do this is not well documented [1].

Suppose you want to report all reasons for failure in converting a `std::string` to a non-negative `int`. The list is:

- `EmptyString` – the input string is empty,
- `IllegalChar` – input contains characters that are not digits,
- `TooLong` – input represents a number, but this number would not fit into a variable of type `int`.

    #include <boost/system/error_code.hpp>  // bring in boost::system::error_code et al
    #include <iostream>
    #include <string>  // for string printing

    // This is the custom error code enum
    enum class ConversionErrc
    {
      Success = 0,  // 0 should not represent an error
      EmptyString = 1,
      IllegalChar = 2,
      TooLong = 3,
    };

    namespace boost
    {
      namespace system
      {
        // Tell the C++ 11 STL metaprogramming that enum ConversionErrc
        // is registered with the standard error code system
        template <> struct is_error_code_enum<ConversionErrc> : std::true_type
        {
        };
      }  // namespace system
    }  // namespace boost

    namespace detail
    {
      // Define a custom error code category derived from boost::system::error_category
      class ConversionErrc_category : public boost::system::error_category
      {
      public:
        // Return a short descriptive name for the category
        virtual const char *name() const noexcept override final { return "ConversionError"; }
        // Return what each enum means in text
        virtual std::string message(int c) const override final
        {
          switch(static_cast<ConversionErrc>(c))
          {
          case ConversionErrc::Success:
            return "conversion successful";
          case ConversionErrc::EmptyString:
            return "converting empty string";
          case ConversionErrc::IllegalChar:
            return "got non-digit char when converting to a number";
          case ConversionErrc::TooLong:
            return "the number would not fit into memory";
          default:
            return "unknown";
          }
        }
        // OPTIONAL: Allow generic error conditions to be compared to me
        virtual boost::system::error_condition default_error_condition(int c) const noexcept override final
        {
          switch(static_cast<ConversionErrc>(c))
          {
          case ConversionErrc::EmptyString:
            return make_error_condition(boost::system::errc::invalid_argument);
          case ConversionErrc::IllegalChar:
            return make_error_condition(boost::system::errc::invalid_argument);
          case ConversionErrc::TooLong:
            return make_error_condition(boost::system::errc::result_out_of_range);
          default:
            // I have no mapping for this code
            return boost::system::error_condition(c, *this);
          }
        }
      };
    }  // namespace detail

    // Define the linkage for this function to be used by external code.
    // This would be the usual __declspec(dllexport) or __declspec(dllimport)
    // if we were in a Windows DLL etc. But for this example use a global
    // instance but with inline linkage so multiple definitions do not collide.
    #define THIS_MODULE_API_DECL extern inline

    // Declare a global function returning a static instance of the custom category
    THIS_MODULE_API_DECL const detail::ConversionErrc_category &ConversionErrc_category()
    {
      static detail::ConversionErrc_category c;
      return c;
    }

    // Overload the global make_error_code() free function with our
    // custom enum. It will be found via ADL by the compiler if needed.
    inline boost::system::error_code make_error_code(ConversionErrc e)
    {
      return {static_cast<int>(e), ConversionErrc_category()};
    }

    int main(void)
    {
      // Note that we can now supply ConversionErrc directly to error_code
      boost::system::error_code ec = ConversionErrc::IllegalChar;

      std::cout << "ConversionErrc::IllegalChar is printed by boost::system::error_code as "
        << ec << " with explanatory message " << ec.message() << std::endl;

      // We can compare ConversionErrc containing error codes to generic conditions
      std::cout << "ec is equivalent to boost::system::errc::invalid_argument = "
        << (ec == std::errc::invalid_argument) << std::endl;
      std::cout << "ec is equivalent to boost::system::errc::result_out_of_range = "
        << (ec == std::errc::result_out_of_range) << std::endl;
      return 0;
    }

View this code on Github

This might look like a lot of extra boilerplate over simply using your custom error code enum directly, but look at the advantages:

1. Any code which can speak `boost::system::error_code` can now work with errors from your code, AND without being recompiled.
2. `boost::system::system_error` can now wrap your custom error codes seamlessly, allowing your custom error code to be converted into a C++ exception and back out again without losing information.
3. `boost::system::error_code` knows how to print itself, and will print your custom error code without extra work from you. As usually you’d need to define a print routine for any custom error code you’d write anyway, there is actually very little extra boilerplate here.
4. If you implement the `default_error_condition()` override, you can allow code exclusively written to understand `boost::system::errc` alone to examine your custom error code domain for equivalence to the standard error conditions, AND without being recompiled.

note

This documentation recommends that when you define your custom `enum` for representing `error_code`s, you should always make sure that value 0 never represents an actual error: it should either represent a success or should not be provided at all. If you only intend to use your `enum` inside `result<>` or `outcome<>` you can just start your enumerations from 1. If you intend to also return `boost::system::error_code` directly from functions, you should probably define value 0 as success, so that you are able to inform about function’s success by returning `MyEnum::Success`. This is because `error_code`’s contextual conversion to `bool` (which some people use to check if there was an error or not) only checks for the numeric value of the error code (without looking at error domain (category)).

[1]: The only documentation I’m aware of is the quite old guide by Chris Kohlhoff, founder of ASIO and the Networking TS:

- http://blog.think-async.com/2010/04/system-error-support-in-c0x-part-1.html
- http://blog.think-async.com/2010/04/system-error-support-in-c0x-part-2.html
- http://blog.think-async.com/2010/04/system-error-support-in-c0x-part-3.html
- http://blog.think-async.com/2010/04/system-error-support-in-c0x-part-4.html
- http://blog.think-async.com/2010/04/system-error-support-in-c0x-part-5.html

Last revised: September 10, 2021 at 11:56:40 +0100