【问题标题】:C++ variadic function template with multiple typelists具有多个类型列表的 C++ 可变参数函数模板
【发布时间】:2022-02-28 06:38:51
【问题描述】:

Compiler Explorer Demonstration 显示了我发现的有效内容,以及一个注释掉的部分,显示了我想要的内容,但这不起作用。

我是 C++ 新手,我正在尝试用 C++20 编写一个 sqlite3 接口,该接口对查询参数和返回的列类型进行类型检查。我已经被困了好几天了,读了这么多。我确信其中一个有我的答案,但我只是不太了解这些东西,无法弄清楚我的问题是什么:

最终,这就是我想要的工作

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);
}

任何意见将不胜感激。

【问题讨论】:

  • 请将您的代码作为文本添加到问题中。你遇到的具体问题是什么?您是否在问如何遍历两个列表中的所有类型?还可以考虑使用现有的包装器,例如sqlite++.
  • @HolyBlackCat 更新了更完整的示例。 Compiler Explorer 链接也有一些紧密的工作解决方案,但不是我想要的。我遇到的确切问题是我想要的界面无法编译。我可以使用折叠表达式遍历所有类型并使用index_sequence 来获取相对位置。这是一个学习练习,因此包装器会适得其反。谢谢。
  • 我明白了。由于不允许部分特化函数,因此您需要一个包含静态函数的辅助模板类(您将要对其进行部分特化)。将所有逻辑移至该辅助函数,并从现有函数中调用它。
  • “我是 C++ 新手,我正在尝试用 C++20 编写一个 sqlite3 接口” 标记为“可变参数模板”和“元编程”... ?? ??好吧,这迅速升级。也许从简单开始? ??????
  • 我不明白这应该如何工作。您是否尝试执行类似std::format 的操作,但对于 sql 查询?基本上,你写的是类型安全的sqlite_exec_printf吗?我不明白你为什么需要Typelist&lt;int, float&gt; 你已经有了类型,1 是一个int42.0f 是一个浮点数,不需要指定它们。因为这是一个很好的理论例子,但它可能是你在问 XY 问题。

标签: c++ metaprogramming variadic-templates template-meta-programming partial-specialization


【解决方案1】:

您确定需要模板专业化吗?

简单地使用重载怎么样?

如果在模板声明中切换Input.../Output...

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

您可以在调用中显式显示Output... 列表,并从inputs... 参数推导出Input... 列表

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

您可以拨打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

如果您只是将 Query2() 重命名为 Query(),请注意您的编译器会探索示例编译。

以下是完整的编译示例

#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);
}

【讨论】:

    【解决方案2】:

    std::format 非常困难。使用与 std::cout 相同的方法 - 您可以将 &lt;&lt; 添加到一个对象。

    希望这个用很短的时间编写的简短示例向您展示如何使用sqlite3_str_appendf 构造查询字符串以及如何转换输出参数,但最重要的是鼓励更多地了解 C++编程。这只是在很短的时间内编写的用于显示界面的模板,未经测试 - 真正的界面应该经过深思熟虑。

    #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';
        }
    }
    

    【讨论】:

    • 谢谢你 - 很明显你在这个答案中做了很多工作,我很感激。我最初的帖子纯粹是我感兴趣的学习练习。我确实有多年的学习经验,你的回答将使我有机会学习更多。
    猜你喜欢
    • 1970-01-01
    • 2019-04-25
    • 1970-01-01
    • 1970-01-01
    • 2020-11-16
    • 2013-09-13
    • 2021-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多