2013-04-02 2 views
6

Cで書かれたSQLライブラリの周りにC++ 11ラッパーを実装しようとしています.Cライブラリには、列インデックスを必要とするSQL文から別のデータ型を取得するための別の関数があります。単純なアプローチは以下のプロトタイプですが、重大な欠陥があります。引数の実行順序に依存します。これは安全ではありません(コンパイラエラーがあり、テストしていない可能性があります)。可変テンプレートの展開でintをインクリメントする安全な方法は何ですか?

質問:バリデーションテンプレートの展開で変数を安全にインクリメントするプラットフォームに依存しない方法は何ですか?

template< typename... ColumnTypes > 
void SQLStatement::execute(std::function< void(ColumnTypes...) > rowCallback){ 
    while(this->nextRow()){ 
     int column = 0; 
     rowCallback(this->getColumn<ColumnTypes>(column++)...); 
     //       unreliable increment^
    } 
} 

template< typename T > 
T SQLStatement::getColumn(const int columnIdx){} 

template<> 
inline int SQLStatement::getColumn<int>(const int columnIdx){ 
    return sql_library_column_int(this->nativeHandle, columnIdx); 
} 

// Other getColumn specializations here... 
+0

毎回カラムをゼロに設定しています。 – jthill

+0

@jthill:行から値を抽出するときに、各行でゼロを再計算します。 –

+0

参照を取り、その参照をインクリメントして古い値を返す単純な関数を作るのはなぜですか? –

答えて

4

mfontaniniのソリューションは動作しますが、コンパイル時にインクリメントするカラムインデックスの計算を実行するので良いですが、の質問への直接的な答えがあることを指摘する価値があると思います。パック展開。 (残念ながら、バグのためにGCCで動作しないようですが、最後の警告を参照してください)

答えは、関数呼び出しの引数の評価が順序付けられていないのに対し、 リストの初期化に引数はない:ブレース-INIT-リストの初期リスト内

(§8.5.4/ 4)、パックの拡張から生じるいずれかを含むイニシャライザ条項、( 14.5.3)は、出現順に評価される。つまり、与えられたinitializer-clauseに関連するすべての値の計算と副作用は、イニシャライザのコンマで区切られたリストの中でそれに続くinitializer-clauseに関連付けられたすべての値の計算と副作用の前に順序付けられます。
[注:この評価順序は、初期化のセマンティクスに関係なく保持されます。たとえば、通常、呼び出しの引数にシーケンス制約がない場合でも、initializer-listの要素がコンストラクタ呼び出しの引数として解釈されたときに適用されます。これはstd::tuple使用してリストを初期化し

rowCallback(std::tuple<ColumnTypes...> { getColumn<ColumnTypes>(column++)... }); 

: -

したがって、あなたはブレース-INIT-リストに基づいて何かにあなたの関数呼び出しを変換する場合、エンドノート]は、所望の効果を得られます - 初期化(中括弧{ ... }に気付く)ので、column++の副作用は左から右の順序で実行されます。

上記のように書くと、rowCallback()を変更して、引数のリストの代わりにstd::tupleを受け入れる必要があることを意味します。これが気に入らない場合は、別のテンプレート関数call_on_tuple(fun,tup)を作成して、funという関数を、タプルtupを展開した結果の引数で呼び出します。私は一度これを行う方法を説明しましたhere、または好きな場合は、rlxutil::call_on_tuplemy GitHub repositoryから使用できます。

あなたexecute機能は、次のようになります。

template <typename... ColumnTypes> 
void execute(function<void(ColumnTypes...)> rowCallback) 
{ 
    using std::tuple; 
    using rlxutil::call_on_tuple; 

    int column = 0; 
    call_on_tuple(rowCallback, 
       tuple<ColumnTypes...> { getColumn<ColumnTypes>(column++)... }); 
} 

警告:これはGCCとの期待どおりに動作しませんです。私はこれがここに報告されたバグのためだと信じています:http://gcc.gnu.org/bugzilla/show_bug.cgi?id=51253

+1

ああ、それは確かに興味深い事実です。通常の関数呼び出しのセマンティクスとは異なり、関数呼び出しにスワップして突然間違った順序を取得するのは簡単すぎますが、実際には面白い違いです... –

+0

私はあなたが説明する方法とまったく同じイニシャライザーリストですが、イニシャライザーリストからタプルを作成できるとは思いませんでした。私はGCCを使用していますので、@ mfontaniniの答えを実装する必要がありますが、より正確に質問に答えます。 –

5

これは動作するようです。

#include <functional> 
#include <iostream> 
#include <cstddef> 

void foo(int a, float b, int c) { 
    std::cout << a << ", " << b << ", " << c << std::endl; 
} 

template<typename T> 
T getColumn(int index) { 
    return T(index); 
} 

template<size_t... indexes> 
struct index_tuple {}; 

template<size_t head, size_t... indexes> 
struct index_tuple<head, indexes...> { 
    typedef typename index_tuple<head-1, head-1, indexes...>::type type; 
}; 

template<size_t... indexes> 
struct index_tuple<0, indexes...> { 
    typedef index_tuple<indexes...> type; 
}; 

template<typename... Args> 
struct make_index_tuple { 
    typedef typename index_tuple<sizeof...(Args)>::type type; 
}; 

template<typename... ColumnTypes, size_t... indexes> 
void execute(const std::function<void(ColumnTypes...)> &callback, index_tuple<indexes...>) { 
    // this should be done for every row in your query result 
    callback(getColumn<ColumnTypes>(indexes)...); 
} 

template<typename... ColumnTypes> 
void execute(const std::function<void(ColumnTypes...)> &callback) { 
    execute(
     callback, 
     typename make_index_tuple<ColumnTypes...>::type() 
    ); 
} 

int main() { 
    std::function<void(int, float, int)> fun(foo); 
    execute(fun); 
} 

デモhere:あなたはただ2つのものを適応させなければならないでしょう。 fooは、getColumnreturn T(index);のように、インデックスが正しくインクリメントされていることを示すためにのみ使用されます。

+0

うわー、これはどちらも良い答えです。@ jogojapanの答えに言及したGCCのエラーのためにあなたの実装を行いますが、彼はより正確に質問に正確に答えます。 –