Информация об изменениях

Сообщение Вопрос целесообразности обобщения от 05.07.2023 13:43

Изменено 05.07.2023 13:57 Sm0ke

Вопрос целесообразности обобщения
Начну пожалуй с конкретной задачи, которая вывела меня на эти размышления.

Неспешно делаю парсер своего яп. В отличии от си++ в языке нет требования на forward declaration. И нет глобальных переменных.
Есть модули, в них есть модульные переменные (aka свойства модуля).
При инициализации переменной модуля в выражении может быть обращение к другой переменной, которая ещё не была определена.
В этом случае подставляется временный номер, а указатель на структуру, которая её хранит кладётся в специальную пачку неизвестных переменных.
Когда будет встречена новая переменная — сперва ей присваивается номер, затем происходит проверка есть ли её имя в той спец пачке.
Если есть, то по всем собранным указателям к имени этой переменной устанавливается актуальный номер, и её имя вычеркивается из пачки неизвестных.

Когда все модули будут загружены, то смотрим: остались ли в спец пачке неизвестные переменные, чтобы выдать при этом лог по ним.

Как лучше организовать пачку? Я думаю взять готовый std::map

Заглянем в доки std::map, и видим — там value_type это std::pair со свойствами first, second (вроде запомнить не сложно)
Метод для добавления элемента в мапу try_emplace() возвращает пару из итератора и флага, было ли добавлено.

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-ый стандарт упрощает определение операторов сравнения для структур.

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 в си++ (тут он лишь для упрощения).
Вопрос целесообразности обобщения
Начну пожалуй с конкретной задачи, которая вывела меня на эти размышления.

Неспешно делаю парсер своего яп. В отличии от си++ в языке нет требования на 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-ый стандарт упрощает определение операторов сравнения для структур.

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 в си++ (тут он лишь для упрощения).