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