Я загружаю различные данные из базы данных и уже реализовал метод шаблона:
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-термин)...
Я как раз собирался оставить этот комментарий, но меня опередили. Используйте std::optional<R>
.
Спасибо вам обоим, но это все равно будет map
, не так ли? Не set
и даже не unordered_map
...
На моей работе (с Java) мы столкнулись с этой проблемой и в итоге получили ядро template<Container> void queryAll(Container<T>&)
, которое выполняло настоящую работу, а затем три вспомогательные обертки: List<T> queryAll()
, T queryOneOrThrow()
и Optional<T> queryOneOrNone()
перегрузки.
@MooingDuck, в Java есть информация о типе времени выполнения (RTTI)! Вы можете создавать новые классы на лету во время выполнения, а затем создавать объекты этих вновь созданных типов для каждого SQL-запроса на основе имен/типов возвращаемых столбцов. Хотя в С++ такой (дорогой) роскоши нет...
@MikhailT.: Верно, но нам не нравится RTTI, поэтому у нас есть передача вызывающей стороны в контейнере в основном методе, который я показал. То, что мы сделали, на 100% выполнимо на C++. Опять же, это C++, так что, может быть, вся концепция была бы лучше в виде итератора?
У вас могут быть перегрузки
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
? Действительно ли для этих двух типов требуется отдельный метод, даже если код идентичен?
Вы все еще можете использовать концепции SFINAE или С++ 20.
Нет, он должен работать с -std=gnu++0x
, используя g++-4.4.
SFINAE работает даже до C++11, но обычно нам приходится переписывать некоторые утилиты, представленные в стандартной версии C++11.
@МихаилТ. Нет, он должен работать с -std=gnu++0x с использованием g++-4.4 -- boost может быть вашим другом.
clang++ выдает ошибку -- constexpr, если это расширение C++17 :(
@MikhailT.: Вы можете использовать трейт-функции для реализации переменных частей, а не constexpr if.
но тогда у вас есть перегрузки, и простая перегрузка может быть проще, чем перегрузки с SFINAE.
Решив проблему различения карт и наборов, я смог реализовать разные insert
-функции для разных типов контейнеров.
Моя шаблонная функция loadMap
просто вызывает insert(store, key, value)
-- и правильная реализация insert
выбирается во время компиляции в зависимости от типа store
. (Для наборов value
игнорируется.)
Вы можете использовать
map<const string, std::optional<R>>