Plugging a library into std::error_code - Boost.Outcome documentation

# Plugging a library into std::error_code`

See here for this guide, but for `boost::system::error_code`.

This section illustrates how you can hook into the `std::error_code` system from the Standard Library 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 the C++ STL 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 <iostream>
    #include <string>        // for string printing
    #include <system_error>  // bring in std::error_code et al

    // 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 std
    {
      // Tell the C++ 11 STL metaprogramming that enum ConversionErrc
      // is registered with the standard error code system
      template <> struct is_error_code_enum<ConversionErrc> : true_type
      {
      };
    }

    namespace detail
    {
      // Define a custom error code category derived from std::error_category
      class ConversionErrc_category : public std::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 std::error_condition default_error_condition(int c) const noexcept override final
        {
          switch (static_cast<ConversionErrc>(c))
          {
          case ConversionErrc::EmptyString:
            return make_error_condition(std::errc::invalid_argument);
          case ConversionErrc::IllegalChar:
            return make_error_condition(std::errc::invalid_argument);
          case ConversionErrc::TooLong:
            return make_error_condition(std::errc::result_out_of_range);
          default:
            // I have no mapping for this code
            return std::error_condition(c, *this);
          }
        }
      };
    }

    // 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 std::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
      std::error_code ec = ConversionErrc::IllegalChar;

      std::cout << "ConversionErrc::IllegalChar is printed by std::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 std::errc::invalid_argument = "
        << (ec == std::errc::invalid_argument) << std::endl;
      std::cout << "ec is equivalent to std::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 `std::error_code` can now work with errors from your code, AND without being recompiled.
2. `std::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. `std::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 `std::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 `std::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: June 21, 2019 at 22:46:55 +0100