Home > database >  C variadic function template with multiple typelists
C variadic function template with multiple typelists

Time:02-28

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:

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';
    }
}
  • Related