Zajmuję się tworzeniem biblioteki, która zajmuje się nie wpisywanymi funkcjami C (SQLite) i chcę ją mocno napisać.Rozszerzanie variadyczne C++ za pomocą dedukcji
Jest to silny typ, który umożliwia użytkownikowi powiązanie typów surowych takich jak int, double i std :: string ze słabymi typami db. Moim problemem jest to, że semantyczna biblioteka jest bardzo ciężka i chciałbym dodać automatyczne odliczanie typów.
Mam więc kilka "podstawowych typów":
namespace FieldType {
struct Integer { using rawtype = int; };
struct Real{ using rawtype = double; };
struct Text{ using rawtype = std::string; };
struct Blob{ using rawtype = std::vector<uint8_t>; };
}
Mam też insert
a query
funkcji, które umożliwiają wstawianie i odpytywanie bez użycia tabel SQL. Zapytanie będzie proste. Tak czy inaczej. Zamierzone zastosowanie ma:
FieldDef<FieldType::Integer> mId = makeFieldDef("id", FieldType::Integer()).primaryKey().autoincrement();
FieldDef<FieldType::Text> mName = makeFieldDef("name", FieldType::Text());
FieldDef<FieldType::Integer> mValue = makeFieldDef("value", FieldType::Integer());
SQLiteTable::insert(std::make_tuple(mName, mValue), std::make_tuple(record.name, record.value));
std::vector<Record> r;
SQLiteTable::query
(std::make_tuple(mName, mValue), [&r](std::tuple<std::string, int> res) {
r.push_back(Record{std::get<0>(res), std::get<1>(res)});
});
I wdrożone wkładki w ten sposób:
template <typename ...Ts, typename ...Us>
bool insert (std::tuple<Ts...> def, std::tuple<Us...> values) {
std::ostringstream ss;
ss << "INSERT INTO " << mName << "("
<< buildSqlInsertFieldList<0>(def)
<< ") VALUES ("
<< buildSqlInsertValuesListPlaceholder<0>(values)
<< ");";
auto stmt = newStatement(ss.str());
bindAllValues<0>(stmt.get(), values);
return execute(stmt.get());
}
Działa to dobrze, problemy pochodzą z kwerendy:
template <typename ...Ts, typename ...Us>
void query(std::tuple<Ts...> def, std::function<void(std::tuple<Us...>)> resultFeedbackFunc) {
...
}
Kiedy nazywając go kompilator jest w stanie poprawnie wyprowadzić typy, więc myślę, że wymaga pedantycznej konstrukcji:
SQLiteTable::query<FieldType::Text, FieldType::Integer, /* whatever */> (...)
To niepraktyczne i gadatliwe.
Czy można uprościć funkcję zapytania? Ponieważ mamy ograniczenie w użyciu, to jest to pakiet
Us
, który może być tylko zgodny z typemFieldType::*:rawtype
. Pytam, czy możliwe byłoby użycie jakiegoś konstruktu, który rozpakowuje i stosuje metodę. W przypadkuinsert
, to może być uproszczone coś takiego:template<typename Ts...> bool insert (std::tuple<Ts...> def, std::tuple<Ts::rawtype ...> values)
Zamiast używać krotek, co na temat korzystania pakiety o zmiennej liczbie argumentów? Nie testowałem go, ale obawiam się, że przy użyciu coś jak
template<typename Ts..., typename Us....> bool insert (Ts... def, Us ... values)
byłoby pomylić kompilator i będzie pogorszyć sytuację. Co myślisz?
- Jeśli możliwe jest zastosowanie rzeczywistej implementacji zapytania, jakie byłoby obejście, aby kod użycia był bardziej wyrazisty?
Oto kilka szczegółów na temat kodu, aby wyjaśnić:
Funkcja zapytanie jest realizowane za pomocą następującego Pseudokod:
template <typename ...Ts, typename ...Us>
void query(std::tuple<Ts...> def, std::function<void(std::tuple<Us...>)> resultFeedbackFunc) {
std::ostringstream ss;
ss << "SELECT " << buildSqlInsertFieldList<0>(def) << " FROM " << mName <<";";
auto stmt = newStatement(ss.str());
auto r = execute(stmt.get());
SQLiteException::throwIfNotOk(r, db()->handle());
while (hasData(stmt.get())) {
auto nColumns = columnCount(stmt.get());
if (nColumns != sizeof...(Ts))
throw std::runtime_error("Column count differs from data size");
std::tuple<Us...> res;
getAllValues<0>(stmt.get(), res);
resultFeedbackFunc(res);
}
};
Statement
jest nieprzezroczysty typ, który ukrywa sqlite
struktura instrukcji, podobnie jak inne funkcje stosowane w metodzie query
, Funkcja getAllValues
wykorzystuje rekursję do wypełnienia tuple
. Tak więc funktor resultFeedbackFunc()
będzie wywoływany dla każdego wiersza bazy danych. Zatem kod klienta może na przykład wypełnić kontener (jak wektor).
Aktualizacja:
Śledziłem rozwiązanie @ bolov, a dodane ulepszenia @ Massimiliano-Jones.
Jest to poprawne wdrożenie wewnętrznego wywołanie funkcji sprzężenia zwrotnego:
resultFeedbackFunc(getValueR<decltype (std::get<Is>(def).rawType())>
(stmt.get(), Is)...);
getValueR
sprawia wewnętrzne wezwanie do sqlite_column_xxx(sqlite3_stmt *, int index)
. Jeśli rozumiem poprawnie, rozpakowanie działa, ponieważ lista argumentów jest ważnym kontekstem rozpakowywania. Gdybym chciał, aby wywołania były wykonywane poza argumentami, musiałem wykonać składanie (lub obejść, ponieważ używam C++ 11).
dla zainteresowanych, projekt opublikowany jest tutaj: https://github.com/studiofuga/mSqliteCpp – HappyCactus