Я работал над некоторым кодом синтаксического анализа 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 или неопределенное поведение, скрывающееся в другом месте в моей довольно большой базе кода, Я провел следующий тест на 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
? Это стандартное поведение?
РЕДАКТИРОВАТЬ: Вот гораздо более простой источник путаницы:
Похоже, что если я использую <
для сравнения любой пары последних трех значений класса перечисления , он всегда возвращает 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