I am developing a library that deals with non-typed C (SQLite) functions, and I want it to print it.
The idea is to have FieldDef strong type that allows the user to associate raw types of the int, double, and std :: string types with weak db types. My problem is that the semantics of the library are very heavy, and I would like to add some automatic type inference.
So, I have a bunch of "basic types":
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>; }; }
I also have insert and query functions that allow you to insert and query tables without using SQL statements. The inquiry will be a simple choice. Anyway. Intended Use:
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 implemented this insert:
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()); }
This works great, problems arise with the request:
template <typename ...Ts, typename ...Us> void query(std::tuple<Ts...> def, std::function<void(std::tuple<Us...>)> resultFeedbackFunc) { ... }
When called, its compiler cannot correctly infer types, so I assume this requires a pedantic construct:
SQLiteTable::query<FieldType::Text, FieldType::Integer, > (...)
It is impractical and verbose.
Is it possible to simplify the query function? Since we have a restriction in use, that is, the Us package can only be of some type compatible with FieldType::*:rawtype , I ask if it is possible to use some construction that decompresses and applies the method. In the case of insert one could simplify something like:
template<typename Ts...> bool insert (std::tuple<Ts...> def, std::tuple<Ts::rawtype ...> values)
instead of using tuples, how about using Variadic packages? I have not tested it, but I'm afraid that something like
template<typename Ts..., typename Us....> bool insert (Ts... def, Us ... values)
it will confuse the compiler and make the situation worse. What do you think?
- If you can use the actual implementation of the query, what would be a workaround to make the usage code more expressive?
Here are some details about the code to explain:
The request function is implemented using the following pseudo-code:
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 is an opaque type that hides the structure of the sqlite operator, like the other functions used in the query method newStatement , execute and columnsCount . The getAllValues function uses recursion to populate a tuple . Therefore, the resultFeedbackFunc() functor will be called for each row in the database. Thus, the client code can, for example, fill a container (for example, a vector).
Update
I followed @bolov's solution and added improvements to @ massimiliano-jones.
This is the correct implementation of the internal callback function call:
resultFeedbackFunc(getValueR<decltype (std::get<Is>(def).rawType())> (stmt.get(), Is)...);
getValueR makes an internal call to sqlite_column_xxx(sqlite3_stmt *, int index) . If I understand correctly, unpacking works because the argument list is a valid context for unpacking. If I wanted the calls to be made outside of the arguments, I had to do a fold (or a workaround since I am using C ++ 11).