I understand the difference between std::error_code
and std::error_condition
, and I understand that std::errc
is an "error condition" enum and not an "error code" enum. I understand how to compare resulting std::error_code
values against "error conditions" and so on. I have read Chris Kohlhoff's blog post about these types several times over the years. I have used these types and extensions to them in production code.
However, I do not understand why std::make_error_code(std::errc)
exists.
It doesn't seem to be necessary for comparing actual std::error_code
values against std::error_condition
values: there is std::make_error_condition(std::errc)
and conversions to std::error_condition
and special comparison overloads for all of that.
The function's existence is especially puzzling because std::is_error_code_enum_v<std::err>
is false
for std::errc
. Presumably that is to prevent implicitly converting std::errc
to std::error_code
via std::make_error_code(std::errc)
, but it isn't clear to me why preventing that is desirable when std::make_error_code(std::errc)
exists. In other words, if you shouldn't make an std::error_code
out of std::errc
, why is there a function that does just that? And if you should, why is that implicit constructor disabled?
Is it std::errc
a special case because code often wants to actually produce a real std::error_code
in the std::generic_category()
with an std::errc
value? In other words, is std::errc
in some ways both an "error code" and an "error condition" enumeration? (And, if so, why isn't std::is_error_code_enum_v<std::errc>
true
?)
Is it a special case for some other reason?
Are user-defined error conditions enums supposed to also offer make_error_code()
like std::errc
does, or just make_error_condition()
?
std::errc
has the following abilities:
error_condition
because it has make_error_condition()
defined.error_code
because it has make_error_code()
defined.error_condition
is expected because it is_error_condition_enum
.If the std::errc
were implicitly convertible to the error_code
it would use existing explicit conversion (make_error_code
) to implement the imaginable implicit conversion. On the other hand, make_error_code
is helpful by itself.
We can use make_error_code
in a function that reports an error_code
in case of error:
#include <algorithm>
#include <iostream>
#include <system_error>
#include <utility>
#include <vector>
struct minOrErrorResult {
int result;
std::error_code ec;
};
minOrErrorResult minOrError(const std::vector<int>& vec) noexcept {
if (vec.empty())
return { {}, make_error_code(std::errc::invalid_argument) };
int result = *std::min_element(vec.begin(), vec.end());
return { result, {} };
}
int main() {
const std::vector<int> examples[] = {
{ 3, 1, 2 },
{},
};
for (auto&& vec : examples) {
auto [result, ec] = minOrError(vec);
if (ec)
std::cout << "error: " << ec.message() << " [" << ec << "]\n";
else
std::cout << "result: " << result << "\n";
}
return 0;
}
A similar example of a function that throws a system_error
exception in case of error:
int minOrException(const std::vector<int>& vec) {
if (vec.empty())
throw std::system_error(make_error_code(std::errc::invalid_argument));
int result = *std::min_element(vec.begin(), vec.end());
return result;
}
int main() {
const std::vector<int> examples[] = {
{ 3, 1, 2 },
{},
};
for (auto&& vec : examples) {
try {
auto result = minOrException(vec);
std::cout << "result: " << result << "\n";
} catch (std::system_error& ex) {
std::cout << "error: " << ex.what() << " [" << ex.code() << "]\n";
}
}
return 0;
}
If make_error_code
is not used, we can presume that the std::errc
constant is not interpreted as an error_code
:
// Normally assume that rhs is an `error_condition`, never an `error_code`.
// ↓
if (lhs == std::errc::invalid_argument) {
// …
}