#опытным
Представьте, что вам пришел какой-то запрос с json'ом и вам его нужно переложить в плюсовую структуру и дальше как-то ее обрабатывать. В джейсоне записаны какие-то персональные данные человека, но они не всегда присутствуют в полном составе. Давайте посмотрим на структуру, чтобы было понятнее:
struct PersonalData {
std::string name;
std::string surname;
std::string patronymic;
std::optional<std::string> address;
std::optional<std::string> email;
std::optional<std::string> phone;
};Пусть мы обрабатываем какие-то анкетные данные или что-то в таком духе. И человеку обязательно указать свои ФИО, но адрес, эл. почту и телефон - не обязательно.
Берем джейсон и перекладываем(то есть занимаемся тем, чему 6 лет учат в тех вузах):
PersonalData person{
.name = json["name"],
.surname = json["surname"],
.patronymic = json["patronymic"],
.address = json.HasMember("address") ? json["address"] : std::nullopt,
.email = json.HasMember("email") ? json["email"] : std::nullopt,
.phone = json.HasMember("phone") ? json["phone"] : std::nullopt};Просто, чтобы кучу if'ов не плодить, воспользуемся тернарным оператом. Если в json'е есть данное поле, то инициализируем опциональное поле им, если нет, то std::nullopt'ом.
Ничего криминального не сделали. Вдобавок использовали designated initialization из с++20.
Компилируем ииииииии..... Ошибка компиляции.
Пишет, что тернарный оператор не может возвращать разные типы.
Дело в том, что std::nullopt - это константа типа nullopt_t. А поле джейсона имеет тип строки. Конечно, из обоих типов можно сконструировать объект std::optional. Но тернарный оператор не знает, что мы хотим. Ему просто не разрешается возвращать разные типы.
Но почему? Это же так удобно.
С++ - это не всегда про удобство)
Представьте себе шаблонную функцию, возвращаемый тип которой выводит сам компилятор. Условно - try_stoi. Если строка может быть преобразована в int, то возвращаем число, если нет - то возвращаем нетронутую строку.
auto try_stoi(const std::string& potential_num) {
if (can_be_converted_to_int(potential_num)) {
return std::stoi(potential_num);
} else {
return potential_num;
}
}Разные ветки возвращают неконвертируемые друг в друга типы, поэтому компилятор не сможет вывести единый тип и произойдет ошибка компиляции. Если же явно проставить тип возвращаемого значения std::optional, то все заработает.
Однако для тернарного оператора мы не можем определить тип возвращаемого значения, за нас это неявно делает компилятор. Поэтому, если две ветки условия возвращают неконвертируемые типы, то мозги компилятора бухнут и он отказывается работать..
Придется явно оборачивать все в std::optional:
PersonalData person{
.name = json["name"],
.surname = json["surname"],
.patronymic = json["patronymic"],
.address = json.HasMember("address") ? std::optional(json["address"]) : std::nullopt,
.email = json.HasMember("email") ? std::optional(json["email"]) : std::nullopt,
.phone = json.HasMember("phone") ? std::optional(json["phone"]) : std::nullopt};Это немного снижает читаемость и привносит кучу повторения кода, но имеем, что имеем.
Be flexible. Stay cool.
#cppcore #cpp17 #cpp20