2013-04-02 2 views
6

Ich versuche, einen C++ 11-Wrapper um eine in C geschriebene SQL-Bibliothek zu implementieren. Die C-Bibliothek verfügt über separate Funktionen zum Abrufen verschiedener Datentypen aus einer SQL-Anweisung, die einen Spaltenindex erfordern. Ein einfacher Ansatz ist im Folgenden als Prototyp beschrieben, hat aber einen kritischen Fehler: Er beruht auf der Reihenfolge der Ausführung von Argumenten, die nicht sicher ist (möglicherweise auch Compilerfehler, die nicht getestet wurden).Was ist ein sicherer Weg, ein int in einer variadischen Template-Erweiterung zu erhöhen?

Frage: Was ist ein plattformunabhängiger Weg, um eine Variable in einer variadischen Template-Erweiterung sicher zu inkrementieren?

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

Sie setzen die Spalte jedes Mal auf Null. – jthill

+0

@jthill: Korrigieren, in jeder Zeile starte ich von Spalte Null neu, wenn die Werte aus der Zeile extrahiert werden. –

+0

Warum nicht eine einfache Funktion erstellen, die eine Referenz aufnimmt, diese Referenz inkrementiert und den alten Wert zurückgibt? –

Antwort

4

Während Mfontaninis Lösung funktioniert und gut ist, weil sie die Berechnung des inkrementierenden Spaltenindex zur Kompilierzeit durchführt, denke ich, es lohnt sich, darauf hinzuweisen, dass es auch eine direkte Antwort auf Frage von gibt, wie man ein int in einem variadic erhöht Packexpansion. (Leider scheint es bei GCC aufgrund eines Fehlers nicht zu funktionieren, siehe den Vorbehalt am Ende.)

Die Antwort basiert auf der Tatsache, dass während die Auswertungen von Argumenten in einem Funktionsaufruf unsequenced sind, die Auswertungen von Argumente in einer Liste Initialisierung nicht sind:

(§8.5.4/4) innerhalb der Initialisierer-Liste einer verspannten-init-Liste, die Initialisierer-Klauseln, einschließlich aller, die aus Packungsdehnungen führen (14.5.3), werden in der Reihenfolge ausgewertet, in der sie erscheinen. Das heißt, jede Wertberechnung und jeder Nebeneffekt, der einer gegebenen Initialisierungsklausel zugeordnet ist, wird vor jeder Wertberechnung und jedem Nebeneffekt, der mit einer Initialisierungsklausel verknüpft ist, die ihr folgt, in der kommagetrennten Liste der Initialisierungsliste sequenziert.
[Hinweis: Diese Reihenfolge der Auswertung gilt unabhängig von der Semantik der Initialisierung; zum Beispiel gilt es, wenn die Elemente der Initialisiererliste als Argumente eines Konstruktoraufrufs interpretiert werden, obwohl normalerweise keine Sequenzbeschränkungen für die Argumente eines Aufrufs bestehen. - Endnote]

Deshalb, wenn Sie Ihren Funktionsaufruf in etwas verwandeln, basierend auf einer Doppelpack-init-Liste, werden Sie den gewünschten Effekt erhalten:

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

Dieses initialisiert eine std::tuple mit Liste -Initialisierung (beachten Sie die geschweiften Klammern { ... }), daher wird die column++ Nebeneffekt in der Reihenfolge von links nach rechts durchgeführt werden.

Wenn oben beschrieben, bedeutet dies, dass Sie rowCallback() ändern müssen, damit es eine std::tuple statt einer Liste von Argumenten akzeptiert. Wenn Sie das nicht mögen, können Sie eine separate Template-Funktion call_on_tuple(fun,tup) erstellen, die jede Funktion fun für die Argumente aufruft, die sich aus dem expandierenden Tupel tup ergeben. Ich habe einmal beschrieben, wie man das macht here, oder wenn Sie möchten, könnten Sie rlxutil::call_on_tuple von my GitHub repository verwenden.

Ihre execute Funktion sieht dann wie folgt aus:

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

Caveat: Das ist wie mit GCC erwartet funktioniert nicht. Ich glaube, das liegt an dem hier gemeldeten Fehler: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=51253.

+1

Ah, das ist in der Tat eine interessante Tatsache. Ich mag es nicht, sich darauf zu verlassen, weil es anders ist als die übliche Funktionsaufrufsemantik, und es ist viel zu einfach, zu einem Funktionsaufruf zu wechseln und plötzlich die falsche Reihenfolge zu bekommen, aber es ist tatsächlich ein interessanter Unterschied ... –

+0

hatte ich mit verwendet eine Initialisierungsliste genau so, wie Sie es erklären, aber ich dachte nicht, dass Sie ein Tupel aus einer Initialisierungsliste erstellen könnten. Ich verwende GCC, also muss ich mit der Antwort von @ mfontanini in der Implementierung gehen, aber ich akzeptiere deine, um die Frage genauer zu beantworten. –

5

Dies scheint zu funktionieren. Sie würden nur ein paar Dinge anpassen müssen:

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

Demo here. Beachten Sie, dass die Funktion foo nur verwendet wird, um anzuzeigen, dass die Indizes korrekt inkrementiert werden, genau wie die return T(index); in getColumn.

+0

Wow, das sind beide gute Antworten, und ich werde Ihre aufgrund der GCC-Fehler @ Jogojapan's Antwort erwähnt implementieren, aber er beantwortet die Frage genauer als gefragt. –