Compiler Explorer Demonstration shows what I have found that works as well as a commented out section showing what I want, but that doesn't work.
I am new to C and I am trying to write a sqlite3 interface in C 20 that has type checking for query parameters and returned column types. I have been stuck for several days now and have read so much. I am sure that one of these has my answer, but I just don't understand this stuff well enough to figure out what my issue is:
- Variadic template constructor with multiple parameters packs
- template template variadic parameter pack
- C variadic template return type
- How can I have multiple parameter packs in a variadic template?
- Unpacking a typelist
Ultimately, this is what I would like to work
template <class... T>
struct Typelist {};
struct Database {
Database(const string &sql) {}
template <class Input, class Output>
void Query(Input input) {}
// error: non-class, non-variable partial specialization
// 'Query<Typelist<Inputs ...>, Typelist<Outputs ...> >' is not allowed
template <class... Inputs, class... Outputs>
vector<tuple<Outputs...>> Query<Typelist<Inputs...>, Typelist<Outputs...>>(
const string &sql, Inputs... inputs) {}
};
int main() {
Database db(":memory:");
vector<tuple<string, string>> people =
db.Query<Typelist<int, float>, Typelist<string, string>>(
"SELECT fname, lname FROM users WHERE id = ? AND somefloat = ?;", 1, 42.0f);
}
Any observations would be greatly appreciated.
CodePudding user response:
Are you sure that you need template specialization?
What about simply using overloading?
If you switch the Input...
/ Output...
in the template declaration
template <typename ... Outputs, typename ... Inputs>
you can explicit the Output...
list in the call and the Input...
list is deduced from the inputs...
arguments
std::vector<std::tuple<Outputs...>> Query(const std::string &sql, Inputs... inputs) {}
You can call Query()
as follows
std::vector<std::tuple<std::string, std::string>> people =
db.Query<std::string, std::string>(
"SELECT fname, lname FROM users WHERE id = ? AND somefloat = ?;", 1, 42.0f);A
Observe also that your compiler explore example compile if you simply rename Query2()
as Query()
.
The following is a full compiling example
#include <tuple>
#include <string>
#include <vector>
struct Database
{
Database(std::string const & sql)
{}
template <typename Input, typename Output>
void Query(Input input)
{}
template <typename ... Outputs, typename ... Inputs>
std::vector<std::tuple<Outputs...>>
Query(std::string const & sql, Inputs... inputs)
{}
};
int main()
{
Database db(":memory:");
std::vector<std::tuple<std::string, std::string>> people =
db.Query<std::string, std::string>(
"SELECT fname, lname FROM users WHERE id = ? AND somefloat = ?;",
1, 42.0f);
}
CodePudding user response:
Writing std::format
is extremely hard. Use the same approach as std::cout
- one object that you can <<
add to.
May this short example written in a very short time show you how to use sqlite3_str_appendf
to construct the query string and how to convert output parameters, but most importantly encourage learning a lot more about C programming. This is merely a template written in a very short time to show the interface, untessted - a real interface would be expected to be much more well thought through.
#include <sqlite3.h>
#include <iostream>
#include <stdexcept>
#include <vector>
#include <string_view>
#include <tuple>
#include <functional>
#include <algorithm>
// C callabck for sqlite3_exec
extern "C"
int query_C_trampoline(void *cookie, int n, char **d, char **c) {
auto f = reinterpret_cast<std::function<int(int, char **, char **)>*>(cookie);
return (*f)(n, d, c);
}
// the output conversion functions
template<typename T>
void query_assign(char *data, char *column, T& to);
template<>
void query_assign(char *data, char *column, std::string& to) {
to = data;
}
template<std::size_t I = 0, typename ...T>
void query_assign_recursive(char **data, char **column, std::tuple<T...>& res) {
query_assign(*data, *column, std::get<I>(res));
if constexpr (I 1 != sizeof...(T)) {
query_assign_recursive<I 1>(data , column , res);
}
}
struct Query {
sqlite3_str *s{};
sqlite3 *db{};
Query(sqlite3 *db) : db(db) {
s = sqlite3_str_new(db);
if (s == NULL) throw std::runtime_error("something");
}
~Query() {
// TODO free(s) at least
}
void _herr() {
if (sqlite3_str_errcode(s)) {
throw std::runtime_error("something");
}
}
Query& operator<<(const char *t) {
sqlite3_str_appendf(s, "%s", t);
_herr();
return *this;
}
Query& operator<<(int t) {
sqlite3_str_appendf(s, "%d", t);
_herr();
return *this;
}
Query& operator<<(float t) {
sqlite3_str_appendf(s, "%f", t);
_herr();
return *this;
}
template<typename ...T>
std::vector<std::tuple<T...>> exec() {
std::vector<std::tuple<T...>> ret{};
std::function<int(int, char **, char**)> cb = [&](int count, char **data, char **column) {
std::tuple<T...> col;
if (count != sizeof...(T)) throw std::runtime_error("count");
// recursively assing tuple elements, as above
query_assign_recursive(data, column, col);
ret.emplace_back(col);
return 0;
};
char *errmsg{};
// trampoline only calls std::function
int e = sqlite3_exec(db, sqlite3_str_value(s), query_C_trampoline, &cb, &errmsg);
if (e) std::runtime_error((const char *)errmsg);
return ret;
}
};
int main() {
sqlite3 *ppdb;
sqlite3_open("/tmp/a", &ppdb);
auto res =
(Query(ppdb)
<< "SELECT fname, lname FROM users WHERE id = "
<< 1
<< " AND somefloat = "
<< 42.0f
<< ";"
).exec<std::string, std::string>();
for (auto&& i : res) {
std::cout << std::get<0>(i) << ' ' << std::get<1>(i) << '\n';
}
}