Как реализовать общий для карт и наборов шаблон?

Я загружаю различные данные из базы данных и уже реализовал метод шаблона:

template <typename R>
bool
loadMap(map<const string, R> &store, const char *query) {
{
...
      for (auto row : rows(query)) {
          store.insert(make_pair(row[0], R(row[1], row[2], ....));
      }
}

Метод отправляет query на сервер БД и заполняет store результатами. Первый столбец становится ключом карты, а остальные поля формируют структуру значений. Это работает.

Однако в нескольких случаях запросы возвращают только один столбец, который хранится в set (или unordered_set) — без каких-либо связанных с ним значений.

В настоящее время эти случаи обрабатываются собственными методами, и я хотел бы расширить свою loadMap-функцию на эти другие типы контейнеров — возможно, переименовав ее в loadContainer.

Как будет выглядеть это новое объявление loadContainer и как оно будет вызывать store.insert() только с ключом, без значений?

(Я знаю об уловке превращения набора в карту со всеми пустыми значениями, но я бы хотел этого избежать.)

На самом деле, я даже не могу придумать, как обрабатывать map и unordered_map -- хотя insert будет точно таким же, как мне объявить такую ​​функцию? Два типа карт, похоже, не имеют общего предка (или «интерфейса», если использовать Java-термин)...

Вы можете использовать map<const string, std::optional<R>>

bloody 22.12.2020 20:42

Я как раз собирался оставить этот комментарий, но меня опередили. Используйте std::optional<R>.

PaulMcKenzie 22.12.2020 20:44

Спасибо вам обоим, но это все равно будет map, не так ли? Не set и даже не unordered_map...

Mikhail T. 22.12.2020 20:46

На моей работе (с Java) мы столкнулись с этой проблемой и в итоге получили ядро ​​template<Container> void queryAll(Container<T>&), которое выполняло настоящую работу, а затем три вспомогательные обертки: List<T> queryAll(), T queryOneOrThrow() и Optional<T> queryOneOrNone() перегрузки.

Mooing Duck 22.12.2020 21:22

@MooingDuck, в Java есть информация о типе времени выполнения (RTTI)! Вы можете создавать новые классы на лету во время выполнения, а затем создавать объекты этих вновь созданных типов для каждого SQL-запроса на основе имен/типов возвращаемых столбцов. Хотя в С++ такой (дорогой) роскоши нет...

Mikhail T. 22.12.2020 21:30

@MikhailT.: Верно, но нам не нравится RTTI, поэтому у нас есть передача вызывающей стороны в контейнере в основном методе, который я показал. То, что мы сделали, на 100% выполнимо на C++. Опять же, это C++, так что, может быть, вся концепция была бы лучше в виде итератора?

Mooing Duck 22.12.2020 21:36
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
6
221
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

У вас могут быть перегрузки

template <typename R>
bool loadContainer(std::map<string, R> &store, const char *query) {
{
    // ...
    for (auto row : rows(query)) {
        store.insert(std::make_pair(row[0], R(row[1], row[2] /*, ...*/));
    }
    // ...
}

bool loadContainer(std::set<string> &store, const char *query) {
{
    // ...
    for (auto row : rows(query)) {
        store.insert(row[0]);
    }
    // ...
}

В качестве альтернативы, чтобы избежать перегрузки, вы можете сделать (С++ 17)

template <typename Container>
bool loadContainer(Container& store, const char *query) {
{
    // ...
    for (auto row : rows(query)) {
        if constexpr (is_map<Container>::value) {
            using R = typename Container::mapped_type;
            store.insert(std::make_pair(row[0], R(row[1], row[2] /*, ...*/));
        } else {
            store.insert(row[0]);
        }
    }
    // ...
}

с соответствующими чертами, например

template <typename T, typename Enabler = void>
struct is_map : std::false_type {};

template <typename T>
struct is_map<T, std::void_t<typename T::mapped_type>> : std::true_type {};

Хотелось бы, чтобы вызов rows() действительно был таким же простым :) Но, может быть, мне стоит сделать из него собственный итератор... Кхм... В любом случае, как насчет map против unordered_map? Действительно ли для этих двух типов требуется отдельный метод, даже если код идентичен?

Mikhail T. 22.12.2020 20:57

Вы все еще можете использовать концепции SFINAE или С++ 20.

Jarod42 22.12.2020 21:03

Нет, он должен работать с -std=gnu++0x, используя g++-4.4.

Mikhail T. 22.12.2020 21:06

SFINAE работает даже до C++11, но обычно нам приходится переписывать некоторые утилиты, представленные в стандартной версии C++11.

Jarod42 22.12.2020 21:12

@МихаилТ. Нет, он должен работать с -std=gnu++0x с использованием g++-4.4 -- boost может быть вашим другом.

PaulMcKenzie 22.12.2020 21:16

clang++ выдает ошибку -- constexpr, если это расширение C++17 :(

Mikhail T. 22.12.2020 21:26

@MikhailT.: Вы можете использовать трейт-функции для реализации переменных частей, а не constexpr if.

Davis Herring 22.12.2020 21:49

но тогда у вас есть перегрузки, и простая перегрузка может быть проще, чем перегрузки с SFINAE.

Jarod42 22.12.2020 21:50
Ответ принят как подходящий

Решив проблему различения карт и наборов, я смог реализовать разные insert-функции для разных типов контейнеров.

Моя шаблонная функция loadMap просто вызывает insert(store, key, value) -- и правильная реализация insert выбирается во время компиляции в зависимости от типа store. (Для наборов value игнорируется.)

Другие вопросы по теме