2017-11-16 8 views
7

私は非型C関数(SQLite)を扱うライブラリを開発しており、それを強くタイプしたいと思います。控除を使用したC++可変展開

考え方はint、double、std :: stringなどの生の型を弱いDB型に結びつけることを可能にする強い型のFieldDefを持つことです。 私の問題は、ライブラリのセマンティックが非常に重いため、いくつかの自動タイプの控除を追加したいと思います。

だから私は、「基本タイプ」の束を持っている:

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を持っているし、SQLステートメントを使用せずにテーブルを挿入し、照会できるようquery機能。クエリはプレーンセレクトです。とにかく。意図された使用は、次のとおりです。

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,executeおよびcolumnsCountです。 getAllValues関数は、再帰を使用してtupleを入力します。したがって、ファンクタresultFeedbackFunc()は、データベースのすべての行に対して呼び出されます。したがって、クライアントコードは、例えば、コンテナ(ベクトルのような)を満たすことができます。


更新

私はbolovのソリューション@続き、@のマッシミリアーノ・ジョーンズの改善点を追加しました。

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

getValueRsqlite_column_xxx(sqlite3_stmt *, int index)内部呼び出しを行う:

これは、フィードバック機能に対する内側呼び出しの正しい実装です。私が正しく理解すれば、アンパックは、引数リストがアンパックのための有効なコンテキストであるために機能します。議論の外に呼び出しをしたい場合は、折りたたみ(または私はC++ 11を使用しているので回避策)をしなければなりませんでした。

+0

も、プロジェクトはここで公開されていますhttps://github.com/studiofuga/mSqliteCpp – HappyCactus

答えて

6

投稿に重要なコード部分がないため、特定のヘルプを行うのは難しいです。

しかし、ここに私の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 
} 

無効な呼び出しがに似たメッセージで失敗:控除の対象ではないだろうあなたのポイント2.については

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

。そして、たとえそうであったとしても、パラメータを読みやすくするためにグループ化する必要があります。私。 insert(a, b, c, d)の代わりにinsert({a, b}, {c, d})が必要です。

私はあなたのポイント3を理解していません。

+0

はありがとうございました!これは私が考えていたインターフェースの一種です。私はその質問に他の詳細を入れましたが、あなたがした仮定の大部分は正しいです。あなたの構造を実際のコードに合わせようとすると苦労していますが、私が必要とするすべての情報を持っていると思います。ありがとうございます。約2)、私が推測したようにok。約3を忘れてください)。我々は洗練された解決策を持っているので、回避策は必要ありません。 – HappyCactus

2
  1. タプルを使用する代わりに、Variadic Packを使用するとどうなりますか?

あなたは、これはタプルのバージョンよりも読みやすい

template<typename T,typename V> 
struct FieldInsert{ V const& ref; }; 

template<typename T> 
struct FieldDef 
{ 
    template<typename V> 
    auto operator()(V const& value) const{ return FieldInsert<T,V>{value}; } 
}; 

template<typename... T> 
bool insert(T... args) 
{ 
    // defines buildSqlInsertFieldList and buildSqlInsertValuesListPlaceholder the obvious way ... 
} 

// to be used as 

SQLiteTable::insert(mName(record.name), mValue(record.value)); 

のようなものを試してください:まず、フィールドは、カウント値に自動的に等しいカウント、そして、それぞれの値は、そのフィールドの隣に来ますそれは、query()に関する...


、 'デフォルト' フィールドの値(mName()、たとえば、経由)をサポートし、より表現の選択肢メートルIGHT興味のある人のための

// return a lazily evaluated input-range (or a coroutine?) 
for(auto item: SQLiteTable::query(mName, mValue)) 
    // ... 

// and/or query() simply returns a forwarding wrapper exposing, say, a chainable interface ... 
SQLiteTable::query(mName, mValue).for_each([]/*lambda*/); 
+0

私はあなたの実装が 'insert'、おかげで好きです。 'query'については、私は@ bolovのソリューションを実装しようとしていますが、私はファンクタへの呼び出しを構築することに夢中です:-)ありがとう。 – HappyCactus

関連する問題