Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::make_error_code(std::errc) exist?

Tags:

c++

error-code

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()?

like image 959
Brad Spencer Avatar asked Aug 28 '26 03:08

Brad Spencer


1 Answers

std::errc has the following abilities:

  1. It can be used to create an error_condition because it has make_error_condition() defined.
  2. It can be used to create an error_code because it has make_error_code() defined.
  3. It can participate in operations where an 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) {
        // …
    }
like image 129
Andrei Polushin Avatar answered Aug 29 '26 19:08

Andrei Polushin