<system_error>

From Boost.System documentation:

Error conditions originating from the operating system or other low-level application program interfaces (APIs) are typically reported via an integer representing an error code. When these low-level API calls are wrapped in portable code, such as in a portable library, some users want to deal with the error codes in portable ways. Other users need to get at the system specific error codes, so they can deal with system specific needs.

Goals

The library serves an implementation of specific error handling design. Its core goals are:

  • lightweight non-allocating error representation type, able to be passed by value but still offering some polymorphic behavior

  • ability to define custom error numbers and categories

  • ability to map custom error numbers to a generic category

  • support for converting error values to string messages

  • exception class supporting error representation type if there is a need to report via exception

Design

  • Each error category is (preferably immutable) global object that implements error category interface, which primary function is returning string representation for the given error condition.

  • The error representation type holds only 2 data members: error number (as integer) and a pointer to an instance of error category. There are 2 such types:

    • std::error_code represents platform-dependent error code

    • std::error_condition represents platform-independent error code (rarely used)

  • Error numbers must only be unique within the same category.

  • Error number 0 is a special value and must mean no error in every category.

  • std::system_error is a dedicated exception type to hold error information when an exception object is desired

Synopsis:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
namespace std {

enum class errc { /* constants defined as aliases of POSIX error numbers */ };

template <typename T>
struct is_error_code_enum : false_type {};
template <typename T>
struct is_error_condition_enum : false_type {};
// other specializations (for std::io_errc and std::future_errc) defined in other headers
template <>
struct is_error_condition_enum<errc> : true_type {};

class error_category
{
public:
	virtual ~error_category();
	error_category& operator=(const error_category&) = delete;

	virtual const char* name() const noexcept = 0;

	virtual error_condition default_error_condition(int code) const noexcept
	{ return error_condition(code, *this); }

	virtual bool equivalent(int code, const error_condition& condition) const noexcept
	{ return default_error_condition(code) == condition; }

	virtual bool equivalent(const error_code& code, int condition) const noexcept
	{ return *this == code.category() && code.value() == condition; }

	virtual string message(int condition) const = 0;

	// in C++11 operators: ==, !=, <
	// in C++20 operators: ==, <=>, rest synthesised
	strong_ordering operator<=>(const error_category& rhs) const noexcept;
};

const error_category& generic_category() noexcept; // POSIX error codes
const error_category& system_category() noexcept; // platform-specific error codes

class error_code
{
public:
	error_code() noexcept : error_code(0, system_category()) {}
	error_code(int ec, const error_category& ecat) noexcept : ec(ec), ecat(&ecat) {}
	template <
		typename ErrorCodeEnum,
		typename enable_if<is_error_code_enum<ErrorCodeEnum>::value>::type* = nullptr
	>
	error_code(ErrorCodeEnum e) noexcept;

	template <
		typename ErrorCodeEnum,
		typename enable_if<is_error_code_enum<ErrorCodeEnum>::value>::type* = nullptr
	>
	error_code& operator=(ErrorCodeEnum e) noexcept;

	void assign(int ec, const error_category& ecat) noexcept
	{
		this->ec = ec;
		this->ecat = &ecat;
	}

	void clear() noexcept { *this = error_code(); }

	int value() const noexcept { return ec; }
	const error_category& category() const noexcept { return *ecat; }

	error_condition default_error_condition() const noexcept
	{ return category().default_error_condition(value()); }

	string message() const { return category().message(value()); }

	explicit operator bool() const noexcept { return value() != 0; }

private:
	int ec;
	const error_category* ecat;
};

// in C++11 operators: ==, !=, <
// in C++20 operators: ==, <=>, rest synthesised
strong_ordering operator<=>(const error_code& lhs, const error_code& rhs) noexcept;

template <typename CharT, typename Traits>
basic_ostream<CharT, Traits>& operator<<(basic_ostream<CharT, Traits>& os, const error_code& ec)
{
	return os << ec.category().name() << ':' << ec.value();
}

class error_condition
{
	// [...] analogically same as error_code but:
	// - has no default_error_condition
	// - has no operator<<
};

error_code make_error_code(errc e) noexcept
{ return error_code(static_cast<int>(e), generic_category()); }
error_condition make_error_condition(errc e) noexcept
{ return error_condition(static_cast<int>(e), generic_category()); }

template <>
struct hash<error_code>
{
	size_t operator()(error_code ec) noexcept;
}

class system_error : public runtime_error
{
public:
	system_error(error_code ec, const string& what_arg);
	system_error(error_code ec, const char* what_arg);
	system_error(error_code ec);
	system_error(int ev, const error_category& ecat, const string& what_arg);
	system_error(int ev, const error_category& ecat, const char* what_arg);
	system_error(int ev, const error_category& ecat);

	const error_code& code() const noexcept;
	const char* what() const noexcept override;
};

} // namespace std

Usage

This design works best when:

  • an application needs to work with multiple subcomponents where each has its own error numbering system

  • the error passing mechanism must be lightweight

  • there are no dynamically generated error descriptions - each category has fixed numbers of errors with predetermined descriptions

Implementation

  • define your error numbers (enumeration or an integer type)

  • implement the category class

    • the class definition and its instance can be hidden from other code - you only need to expose a function that returns const std::error_category&

    • override the name function

    • override the message function

    • (optional) override the default_error_condition function if you wish to translate errors to a more generic category (such as std::generic_category()), by default the function does not change the category

  • (if error numbers are enumeration) specialize std::is_error_code_enum

  • (optional) add make_error_code in the same namespace as the definition of error numbers

  • when reporting errors use std::error_code constructor or call make_error_code

The last 3 steps can be repeated analogically for std::error_condition.

Example

The program implements HTTP error numbers taking advantage of standard library features.

header:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <system_error>

namespace lib {

enum class http_error
{
	no_error = 0,

	ok = 200,
	// ...
	bad_request = 400,
	forbidden = 403,
	not_found = 404,
	method_not_allowed = 405,
	// https://en.wikipedia.org/wiki/Hyper_Text_Coffee_Pot_Control_Protocol
	im_a_teapot = 418,
	// ...
};

const std::error_category& http_category() noexcept;

// this function can be picked by ADL
std::error_code make_error_code(http_error e) noexcept
{ return std::error_code(static_cast<int>(e), http_category()); }

} // namespace lib

namespace std {

// this enables std::error_code constructor overload for enums
template <>
struct is_error_code_enum<lib::http_error> : std::true_type {};

}

source:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <lib.hpp>
#include <cerrno>

namespace lib {

namespace {

int map_error_code(int http_code) noexcept
{
	switch (http_code)
	{
		case 0: return 0;
		case 200: return 0;
		// ...
		case 403: return EACCES;
		case 404: return ENOENT;
		case 405: return ENOTSUP;
		// ...
		default: return EINVAL; // there is no better generic POSIX error
	}
}

class http_category_impl : public std::error_category
{
	const char* name() const noexcept override { return "HTTP"; }

	// the goal of this function is to translate errors for specific category to a generic one
	std::error_condition default_error_condition(int code) const noexcept override
	{
		return std::error_condition(map_error_code(code), std::generic_category());
	}

	std::string message(int condition) const override
	{
		switch (condition)
		{
			case 0: return "no error";
			case 200: return "ok";
			// ...
			case 403: return "forbidden";
			case 404: return "not found";
			case 405: return "method not allowed";
			case 418: return "I'm a teapot";
			// ...
			default: return "unknown error";
		}
	}
};
const http_category_impl http_category_instance;

}

const std::error_category& http_category() noexcept { return http_category_instance; }

}