草庐IT

使用推导的 C++ 可变参数扩展

coder 2023-11-17 原文

我正在开发一个处理非类型化 C 函数 (SQLite) 的库,我想对其进行强类型化处理。

想法是拥有一个 FieldDef 强类型,允许用户将原始类型(如 int、double 和 std::string)绑定(bind)到弱数据库类型。 我的问题是库的语义很重,我想添加一些自动类型推导。

所以我有一堆“基本类型”:

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

我还有一个insert 和一个query 函数,它们允许在不使用SQL 语句的情况下插入和查询表。查询将是简单的选择。反正。预期用途是:

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

我是这样实现插入的:

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

这工作正常,问题来自查询:

template <typename ...Ts, typename ...Us>
void query(std::tuple<Ts...> def, std::function<void(std::tuple<Us...>)> resultFeedbackFunc) {
   ...
}

当调用它时,编译器无法正确推导类型,所以我猜它需要一个迂腐的构造:

SQLiteTable::query<FieldType::Text, FieldType::Integer, /* whatever */> (...)

这是不切实际和冗长的。

  1. 可否简化查询功能? 由于我们在使用上有一个限制,即 Us 包只能是与 FieldType::*:rawtype 兼容的某种类型,我想问一下这是否可能使用一些解包和应用方法的构造。在 insert 的情况下,是否可以简化为:

    template<typename Ts...>
    bool insert (std::tuple<Ts...> def, std::tuple<Ts::rawtype ...> values) 
    
  2. 不使用元组,使用可变参数包怎么样?我还没有测试过,但我担心使用类似的东西

    template<typename Ts..., typename Us....>
    bool insert (Ts... def, Us ... values) 
    

会混淆编译器并使事情变得更糟。 你怎么看?

  1. 如果可以使用查询的实际实现,有什么变通方法可以使使用代码更具表现力?

下面是一些关于代码的细节,解释一下:

查询功能使用以下伪代码实现:

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 是一个不透明类型,它隐藏了 sqlite 语句结构,query 方法 newStatement 中使用的其他函数也是如此执行columnsCount。函数 getAllValues 使用递归来填充 tuple。因此,将为数据库的每一行调用仿函数 resultFeedbackFunc()。例如,客户端代码可以填充容器(如 vector )。


更新:

我遵循了@bolov 的解决方案,并添加了@massimiliano-jones 的改进。

这是内部调用反馈函数的正确实现:

resultFeedbackFunc(getValueR<decltype (std::get<Is>(def).rawType())>
      (stmt.get(), Is)...);

getValueRsqlite_column_xxx(sqlite3_stmt *, int index) 进行内部调用。如果我理解正确的话,解包是有效的,因为参数列表是解包的有效上下文。如果我想在参数之外进行调用,我必须进行折叠(或解决方法,因为我使用的是 c++11)。

最佳答案

很难提供具体的帮助,因为您的帖子中缺少重要的代码部分。

但是这是我的 2 美分。请注意,我已经用我的想象力填补了您代码中缺失的部分。

首先你需要去掉 std::function争论。使用 std::function仅当您需要它提供的类型删除时。在您的情况下(至少从您显示的代码来看)您不需要它。所以我们用一个简单的 template <class F> 代替它范围。这解决了演绎问题。

现在,当您传递一个无效的函数对象时,您将在 query 的碗深处得到一个编译错误。执行。如果您不想那样,并且想快速失败,那么有一些选择。我选择用 decltype 向您展示 SFINAE 方法.

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
}

无效调用失败并显示类似于以下内容的消息:

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

关于您的第 2 点。那是不可扣除的。即使是这样,您也希望对参数进行分组以提高可读性。 IE。你想要insert({a, b}, {c, d})而不是 insert(a, b, c, d) .

我不明白你的第 3 点。

关于使用推导的 C++ 可变参数扩展,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47326863/

有关使用推导的 C++ 可变参数扩展的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  10. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

随机推荐