简体   繁体   English

C++ 具有多个类型列表的可变参数 function 模板

[英]C++ variadic function template with multiple typelists

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. Compiler Explorer Demonstration显示了我发现的有效内容,以及显示我想要但不起作用的注释部分。

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.我是 C++ 的新手,我正在尝试用 C++20 编写一个 sqlite3 接口,它对查询参数和返回的列类型进行类型检查。 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.任何意见将不胜感激。

Are you sure that you need template specialization?您确定需要模板专业化吗?

What about simply using overloading?简单地使用重载怎么样?

If you switch the Input... / Output... in the template declaration如果在模板声明中切换Input... / Output...

template <typename ... Outputs, typename ... Inputs>

you can explicit the Output... list in the call and the Input... list is deduced from the inputs... arguments您可以在调用中显式显示Output...列表,然后从输入中推导出Input...列表inputs... arguments

std::vector<std::tuple<Outputs...>> Query(const std::string &sql, Inputs... inputs) {}

You can call Query() as follows您可以按如下方式调用Query()

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() .另请注意,如果您只是将Query2()重命名为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);
}

Writing std::format is extremely hard.编写std::format非常困难。 Use the same approach as std::cout - one object that you can << add to.使用与std::cout相同的方法 - 一个 object 您可以<<添加到。

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.愿这个用很短的时间编写的简短示例向您展示如何使用sqlite3_str_appendf构造查询字符串以及如何转换 output 参数,但最重要的是鼓励学习更多关于 C++ 编程的知识。 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';
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM