Я работал над некоторым кодом разбора JSON, используя прекрасный nlohmann::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
};
Вот моя функция. Я передаю ему 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";
}
Но во время отладки в 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";
}
Это сработало. Прохождение json::value_t::number_float привело к "a floating point number", как я и ожидал.
Все еще любопытно и подозревая, что одна из причуд Microsoft или Undefined Behavior скрывается в другом месте в моей довольно большой базе кода, я провел следующий тест. на 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';
}
И я был действительно напуган, увидев то же поведение, что и Visual Studio:
Without casting enum to underlying type: null: null object: an object array: an array string: a string bool: a boolean int: an integer uint: an integer float: an integer With casting enum to underlying type: null: null object: an object array: an array string: a string bool: a boolean int: an integer uint: an unsigned integer float: a floating point number
Почему это происходит? Похоже, что number_float и number_unsigned считаются равными number_integer. Но, согласно этому ответу, нет ничего особенного в сравнении обычного enum. Есть ли отличия в использовании enum class? Это стандартное поведение?
EDIT: Вот гораздо более простой источник путаницы: кажется, что если я использую < для сравнения любой пары последних трех значений класса enum, он всегда возвращает false. Это, вероятно, сердце моей проблемы выше. Почему такое странное поведение? Следующий вывод взят из этого живого примера.
number_integer < number_integer : false number_integer < number_unsigned : false number_integer < number_float : false number_unsigned < number_integer : false number_unsigned < number_unsigned : false number_unsigned < number_float : false number_float < number_integer : false number_float < number_unsigned : false number_float < number_float : false null < number_integer : true null < number_unsigned : true null < number_float : true bool < number_integer : true bool < number_unsigned : true bool < number_float : true