比较枚举类值时的异常行为

alt*_*gel 4 c++ string enums nlohmann-json

我正在使用可爱的nlohmann :: json进行一些JSON解析代码,并且为了帮助生成有用的错误消息,我为自己创建了一个打印JSON对象类型的函数。该函数接受一个json::value_t,它是一个完全按以下方式定义的枚举类json.hpp

enum class value_t : std::uint8_t {
    null,
    object,
    array,
    string,
    boolean,
    number_integer,
    number_unsigned,
    number_float,
    discarded
};
Run Code Online (Sandbox Code Playgroud)

这是我的职能。我将其传递给json::value_t我,并且希望收到描述它的字符串。

std::string to_string(json::value_t type){
    static const std::map<json::value_t, std::string> mapping = {
        {json::value_t::null,            "null"},
        {json::value_t::object,          "an object"},
        {json::value_t::array,           "an array"},
        {json::value_t::string,          "a string"},
        {json::value_t::boolean,         "a boolean"},
        {json::value_t::number_integer,  "an integer"},
        {json::value_t::number_unsigned, "an unsigned integer"},
        {json::value_t::number_float,    "a floating point number"}
    };
    auto it = mapping.find(type);
    if (it != mapping.end()){
        return it->second;
    }
    return "a mystery value";
}
Run Code Online (Sandbox Code Playgroud)

但是,在Visual Studio中进行调试时,"an integer"当我非常肯定地将其传递给函数时,当该函数返回该字符串时,我确实感到非常震惊json::value_t::number_float

害怕最糟糕的情况,并且想要快速修复,我编写了以下替代方法,该替代方法是相同的,只是在使用枚举之前始终将其枚举转换为其基础类型:

std::string to_string_with_cast(json::value_t type){
    using ut = std::underlying_type_t<json::value_t>;
    static const std::map<ut, std::string> mapping = {
        {static_cast<ut>(json::value_t::null),            "null"},
        {static_cast<ut>(json::value_t::object),          "an object"},
        {static_cast<ut>(json::value_t::array),           "an array"},
        {static_cast<ut>(json::value_t::string),          "a string"},
        {static_cast<ut>(json::value_t::boolean),         "a boolean"},
        {static_cast<ut>(json::value_t::number_integer),  "an integer"},
        {static_cast<ut>(json::value_t::number_unsigned), "an unsigned integer"},
        {static_cast<ut>(json::value_t::number_float),    "a floating point number"}
    };
    auto it = mapping.find(static_cast<ut>(type));
    if (it != mapping.end()){
        return it->second;
    }
    return "a mystery value";
}
Run Code Online (Sandbox Code Playgroud)

这行得通。如我所料,传递了json::value_t::number_float结果"a floating point number"

仍然感到好奇,并且怀疑在我相当大的代码库中潜伏着微软的怪癖或未定义行为之一,我在g ++上运行了以下测试

    std::cout << "Without casting enum to underlying type:\n";
    std::cout << "null:   " << to_string(json::value_t::null) << '\n';
    std::cout << "object: " << to_string(json::value_t::object) << '\n';
    std::cout << "array:  " << to_string(json::value_t::array) << '\n';
    std::cout << "string: " << to_string(json::value_t::string) << '\n';
    std::cout << "bool:   " << to_string(json::value_t::boolean) << '\n';
    std::cout << "int:    " << to_string(json::value_t::number_integer) << '\n';
    std::cout << "uint:   " << to_string(json::value_t::number_unsigned) << '\n';
    std::cout << "float:  " << to_string(json::value_t::number_float) << '\n';

    std::cout << "\nWith casting enum to underlying type:\n";
    std::cout << "null:   " << to_string_with_cast(json::value_t::null) << '\n';
    std::cout << "object: " << to_string_with_cast(json::value_t::object) << '\n';
    std::cout << "array:  " << to_string_with_cast(json::value_t::array) << '\n';
    std::cout << "string: " << to_string_with_cast(json::value_t::string) << '\n';
    std::cout << "bool:   " << to_string_with_cast(json::value_t::boolean) << '\n';
    std::cout << "int:    " << to_string_with_cast(json::value_t::number_integer) << '\n';
    std::cout << "uint:   " << to_string_with_cast(json::value_t::number_unsigned) << '\n';
    std::cout << "float:  " << to_string_with_cast(json::value_t::number_float) << '\n';
}
Run Code Online (Sandbox Code Playgroud)

而且我真的很害怕看到与Visual Studio相同的行为:

enum class value_t : std::uint8_t {
    null,
    object,
    array,
    string,
    boolean,
    number_integer,
    number_unsigned,
    number_float,
    discarded
};
Run Code Online (Sandbox Code Playgroud)

为什么会这样呢?看来number_floatnumber_unsigned都被认为等于number_integer。但是根据这个答案,比较正常并没有什么特别的enum。使用an有什么不同enum class吗?这是标准行为吗?


编辑:这是一个更简单的混淆源:看来,如果我<用来比较最后三个枚举类值中的任何一对,它总是返回false。这可能是我上面提到的问题的核心。为什么会有这种奇怪的行为?以下输出来自此实时示例

std::string to_string(json::value_t type){
    static const std::map<json::value_t, std::string> mapping = {
        {json::value_t::null,            "null"},
        {json::value_t::object,          "an object"},
        {json::value_t::array,           "an array"},
        {json::value_t::string,          "a string"},
        {json::value_t::boolean,         "a boolean"},
        {json::value_t::number_integer,  "an integer"},
        {json::value_t::number_unsigned, "an unsigned integer"},
        {json::value_t::number_float,    "a floating point number"}
    };
    auto it = mapping.find(type);
    if (it != mapping.end()){
        return it->second;
    }
    return "a mystery value";
}
Run Code Online (Sandbox Code Playgroud)

Sla*_*ica 7

您有此问题,因为operator<为此枚举提供了:

inline bool operator<(const value_t lhs, const value_t rhs) noexcept
{
    static constexpr std::array<std::uint8_t, 8> order = {{
            0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */,
            1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */
        }
    };

    const auto l_index = static_cast<std::size_t>(lhs);
    const auto r_index = static_cast<std::size_t>(rhs);
    return l_index < order.size() and r_index < order.size() and order[l_index] < order[r_index];
}
Run Code Online (Sandbox Code Playgroud)

这里

并根据此代码integerunsigned并且float被认为是相等的,提出您的问题。

作为解决方案,您可以使用您的方法,或者简单地将默认比较器替换为lambda或提供std::less不使用此运算符的专门化。