Начну пожалуй с конкретной задачи, которая вывела меня на эти размышления.
Неспешно делаю парсер своего яп. В отличии от си++ в языке нет требования на forward declaration. И нет глобальных переменных.
Есть модули, в них есть модульные переменные (aka свойства модуля).
При инициализации переменной модуля в выражении может быть обращение к другой переменной, которая ещё не была определена.
В этом случае подставляется временный номер, а указатель на структуру, которая её хранит кладётся в специальную пачку неизвестных переменных.
Когда будет встречена новая переменная — сперва ей присваивается номер, затем происходит проверка есть ли её имя в той спец пачке.
Если есть, то по всем собранным указателям к имени этой переменной устанавливается актуальный номер, и её имя вычеркивается из пачки неизвестных.
Когда все модули будут загружены, то смотрим: остались ли в спец пачке неизвестные переменные, чтобы выдать при этом лог по ним.
Как лучше организовать пачку? Я думаю взять готовый std::map
Заглянем в доки std::map, и видим — там value_type это std::pair со свойствами first, second (вроде запомнить не сложно)
Метод для добавления элемента в мапу try_emplace() возвращает другой std::pair с итератором и флагом, было ли добавлено.
my_map v_map;
auto res = v_map.try_emplace(v_key, v_arg);
if( res.second ) { // second -- flag
res.first->second.some_method(); // first -- iterator // second -- значение
}
Согласитесь, что читателю кода было бы нагляднее видеть в качестве value_type структуру со свойствами (key, value).
А метод try_emplace() вместо std::pair<iterator, bool> лучше бы вернул структуру со свойствами (it, was_added).
auto res = v_map.try_emplace(v_key, v_arg);
if( res.was_added ) {
res.it->value.some_method();
}
Красота!
Структура std::pair имеет свойства (first, second), и для неё определены операторы сравнения.
Почему бы не взять её как ключ в std::map ?
using my_key = std::pair<std::string, std::string>; // first -- имя модуля, second -- имя переменной
using my_map = std::map<my_key, my_var_info>;
Или же лучше определить новую структуру со свойствами (module_name, var_name) ??
Тем более конструктор в структуре std::pair не всегда нужен. Без него компиляция должна быть быстрее.
А 20-ый стандарт упрощает определение операторов сравнения для структур.
(Напомню, что ключ для std::map должен быть сравнимым через третий параметр шаблона).
struct my_key {
std::string
module_name,
var_name;
friend bool operator == (const my_key &, const my_key &) = default;
friend auto operator <=> (const my_key &, const my_key &) = default;
};
auto res = v_map.try_emplace({mod_name, var_name});
res.it->key.module_name // против res.first->first.first
? Наглядность кода, или обобщение ?
? заводить ли новую структуру под конкретную цель каждый раз, или взять готовый pair ?
Кстати ещё вопрос: В своих проектах вы используете std::tuple (казалось бы норм тема) ?
И кстати, я стараюсь не использовать auto в си++ (тут он лишь для упрощения).
p.s: Собственно сабж о том, в каких случаях лучше дублировать, а в каких обобщать
Здравствуйте, Sm0ke, Вы писали:
IMHO ты сам через неделю забудешь кто там first кто second
Совсем по-простому, можно переменные завести (вместо комментариев):
auto res = v_map.try_emplace(v_key, v_arg);
auto was_added = res.second;
if( was_added ) {
auto info = res.first->second;
info.some_method();
}
Еще можно вместо структуры сделать типа декодер:
struct decode {
decode(my_map::iterator& it) : module_name(it->first), info(it->second) {}
const my_map::key_type& module_name;
my_map::mapped_type& info;
};
it = ...
decode(it).module_name
decode(it).info
Здравствуйте, bnk, Спасибо за ответ.
bnk>IMHO ты сам через неделю забудешь кто там first кто second
Дак я про что. Разработчики std::map заюзали std::pair имхо зря. Я вообще на данный момент не любитель этого pair с безымянными first и second.
bnk>Совсем по-простому, можно переменные завести (вместо комментариев):
bnk>bnk>auto res = v_map.try_emplace(v_key, v_arg);
bnk>auto was_added = res.second;
bnk>if( was_added ) {
bnk> auto info = res.first->second;
bnk> info.some_method();
bnk>}
bnk>
bnk>Еще можно вместо структуры сделать типа декодер:
bnk>
bnk>struct decode {
bnk> decode(my_map::iterator& it) : module_name(it->first), info(it->second) {}
bnk> const my_map::key_type& module_name;
bnk> my_map::mapped_type& info;
bnk>};
bnk>it = ...
bnk>decode(it).module_name
bnk>decode(it).info
bnk>
Выглядит как костыль. Делать pair + костыль, или сразу завести чёткую структуру с понятными именами.
Здравствуйте, Sm0ke, Вы писали:
S>Согласитесь, что читателю кода было бы нагляднее видеть в качестве value_type структуру со свойствами (key, value).
конечно в теории оно должно быть лучше. Но на пректике мне визуально не нравится
struct MyStruct {
struct MyPair {
std::string key;
std::string value;
};
MyPair insert();
};
MyStruct::MyPair MyStruct::insert() {
}
обилие вложенностей немного смущает. Особенно если кто-то не любит auto.
S>S>auto res = v_map.try_emplace(v_key, v_arg);
S>if( res.was_added ) {
S> res.it->value.some_method();
S>}
S>
S>Красота!
Это не красота. Вот это красота
const auto [it, was_added] = v_map.try_emplace(v_key, v_arg);
auto& [key, value] = *it;
if(was_added) {
value.some_method();
}
Вообще, конечно заводить структуры вроде как правильно и особо тут спорить не о чем, но предложу другой подход: усилить систему типов.
template<typename Name> struct NewStringType {
...
};
using MuduleName = NewStringType<struct ModuleNameHelp>;
using VarName = NewStringType<struct VarNameHelp>;
и тогда особо пофигу: в структуре завёрнуты значения или в тюпле. Можно забыть/ошибиться. Компилятор за всем проследит и "аккуратно но сильно" даст по рукам.
Здравствуйте, sergii.p, Вы писали:
SP>SP>const auto [it, was_added] = v_map.try_emplace(v_key, v_arg);
SP>auto& [key, value] = *it;
SP>if(was_added) {
SP> value.some_method();
SP>}
SP>
Вот бы можно было так без auto :
[my_map::iterator it, bool was_added] = v_map.try_emplace(v_key, v_arg);
SP>Вообще, конечно заводить структуры вроде как правильно и особо тут спорить не о чем, но предложу другой подход: усилить систему типов.
SP>SP>template<typename Name> struct NewStringType {
SP> ...
SP>};
SP>using MuduleName = NewStringType<struct ModuleNameHelp>;
SP>using VarName = NewStringType<struct VarNameHelp>;
SP>
Чтобы нельзя было сравнивать имя модуля с именем переменной?
Вот тут как раз можно бы и обобщить. Такая обёртка подошла бы не только для строк. Назвать к примеру
wrap, и добавить как параметр шаблона хранимый тип.
template <typename T, typename Unique>
struct wrap {
using value_type = T;
using unique_type = Unique;
// data
value_type value;
friend bool operator == (const wrap &, const wrap &) = default;
friend auto operator <=> (const wrap &, const wrap &) = default;
};
Но мне кажется, что на этот случай наверняка должно быть уже готовое решение.
Здравствуйте, Sm0ke, Вы писали:
S>Вот бы можно было так без auto :
S>S>[my_map::iterator it, bool was_added] = v_map.try_emplace(v_key, v_arg);
S>
что за язык? Такое на С++ не компилируется.
S>Вот тут как раз можно бы и обобщить. Такая обёртка подошла бы не только для строк. Назвать к примеру wrap, и добавить как параметр шаблона хранимый тип.
S>Но мне кажется, что на этот случай наверняка должно быть уже готовое решение.
"обобщить" это не про С++
Для целочисленных значений есть давно известная идиома в виде enum class. А для других типов каждый изгаляется как может. Есть к примеру макрос BOOST_STRONG_TYPEDEF. Но честно говоря ради пары десятков строчек заводить зависимость от буста не многим по душе. Может было бы неплохо если бы в языке расширили идиому enum class. Не знаю насколько оно реализуемо. Но вот было бы что-то такое
enum class ModuleName : std::string { Invalid = "" };
и жизнь заиграла бы новыми красками
Здравствуйте, sergii.p, Вы писали:
SP>Здравствуйте, Sm0ke, Вы писали:
S>>Вот бы можно было так без auto :
S>>S>>[my_map::iterator it, bool was_added] = v_map.try_emplace(v_key, v_arg);
S>>
SP>что за язык? Такое на С++ не компилируется.
Я же написал, что мне хотелось бы, чтобы добавили в стандарт такую возможность.
Чтобы можно было использовать Structured binding не через auto, а с конкретными типами)
S>>Вот тут как раз можно бы и обобщить. Такая обёртка подошла бы не только для строк. Назвать к примеру wrap, и добавить как параметр шаблона хранимый тип.
S>>Но мне кажется, что на этот случай наверняка должно быть уже готовое решение.
SP>"обобщить" это не про С++ Для целочисленных значений есть давно известная идиома в виде enum class. А для других типов каждый изгаляется как может. Есть к примеру макрос BOOST_STRONG_TYPEDEF. Но честно говоря ради пары десятков строчек заводить зависимость от буста не многим по душе. Может было бы неплохо если бы в языке расширили идиому enum class. Не знаю насколько оно реализуемо. Но вот было бы что-то такое
SP>SP>enum class ModuleName : std::string { Invalid = "" };
SP>
SP>и жизнь заиграла бы новыми красками
В php ввели enum трёх типов: общие, целочисленные и текстовые. Так что там это можно)
https://www.php.net/manual/en/language.enumerations.backed.php
Здравствуйте, Sm0ke, Вы писали:
S>Я же написал, что мне хотелось бы, чтобы добавили в стандарт такую возможность.
S>Чтобы можно было использовать Structured binding не через auto, а с конкретными типами)
Я так понимаю дальше предложений дело не пошло:
P0480R0 ,
P0480R1