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 typeint
.
#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;
}
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
boost::system::error_code
can now work with errors from your code, AND without being recompiled. 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.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.- If you implement the
default_error_condition()
override, you can allow code exclusively written to understandboost::system::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