c++error-code

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


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


Solution

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