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 typeint
.
#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;
}
This might look like a lot of extra boilerplate over simply using your custom error code enum directly, but look at the advantages:
- Any code which can speak
std::error_code
can now work with errors from your code, AND without being recompiled. 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.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.- If you implement the
default_error_condition()
override, you can allow code exclusively written to understandstd::errc
alone to examine your custom error code domain for equivalence to the standard error conditions, AND without being recompiled.
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