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) {
// …
}