C ++ variational extension using deduction

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, /* whatever */> (...) 

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?

  1. 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).

+7
c ++ c ++ 11 c ++ 14 variadic-templates
source share
2 answers

Concrete help is hard to give, as your message lacks important pieces of code.

However, here are my 2 cents. Please note that I filled my imagination with the missing parts of your code.

First of all, you need to get rid of the std::function argument. Use std::function only if you need to erase the type that it provides. In your case (at least from the code you showed) you don't need this. Therefore, we replace this with a simple parameter template <class F> . This solves the problem of deduction.

Now, when you pass an invalid functor, you get a compilation error in the cups of your query implementation. If you do not want this and want to quickly fail, then there are some options. I decided to show you the SFINAE approach with decltype .

 namespace FieldType { struct Integer { using rawtype = int; }; struct Real{ using rawtype = double; }; struct Text{ using rawtype = std::string; }; }; template <class FT> struct FieldDef { using Type = FT; using RawTye = typename Type::rawtype; auto getRaw() -> RawTye { return {}; } }; template <class... Args, class F, std::size_t... Is> auto query_impl(std::tuple<Args...> def, F f, std::index_sequence<Is...>) -> decltype(f(std::get<Is>(def).getRaw()...), std::declval<void>()) { f(std::get<Is>(def).getRaw()...); } template <class... Args, class F> auto query(std::tuple<Args...> def, F f) -> decltype(query_impl(def, f, std::make_index_sequence<sizeof...(Args)>{})) { query_impl(def, f, std::make_index_sequence<sizeof...(Args)>{}); } 
 auto test() { FieldDef<FieldType::Text> mName = {}; FieldDef<FieldType::Integer> mValue = {}; query(std::make_tuple(mName, mValue), [](std::string, int) {}); // OK query(std::make_tuple(mName, mValue), [](std::string, int, int) {}); // Error query(std::make_tuple(mName, mValue), [](int, int) {}); // Error } 

Invalid calls end with a message similar to:

 error: no matching function for call to 'query' ... note: candidate template ignored: substitution failure [with Args = ...]: no matching function for call to 'query_impl' ... 

Regarding your point 2. This will not be deducted. And even if that were the case, you want to group parameters for readability. That is, you want insert({a, b}, {c, d}) instead of insert(a, b, c, d) .

I do not understand your point 3.

+6
source share
  1. instead of using tuples, how about using Variadic packages?

you can try something like

 template<typename T,typename V> struct FieldInsert{ V const& ref; }; template<typename T> struct FieldDef { template<typename V> auto operator()( V const& value ) const{ return FieldInsert<T,V>{value}; } }; template<typename... T> bool insert( T... args ) { // defines buildSqlInsertFieldList and buildSqlInsertValuesListPlaceholder the obvious way ... } // to be used as SQLiteTable::insert( mName(record.name), mValue(record.value) ); 

this is more readable than the tuple version: firstly, the number of fields is automatically equal to the count values, then each value approaches its own field, it supports the default field values ​​(via, say, mName() ), ...


regarding query() , more expressive alternatives may be

 // return a lazily evaluated input-range (or a coroutine?) for( auto item: SQLiteTable::query( mName, mValue ) ) // ... // and/or query() simply returns a forwarding wrapper exposing, say, a chainable interface ... SQLiteTable::query( mName, mValue ).for_each([]/*lambda*/); 
+2
source share

All Articles