Sequencepoint - Undefiniertes Verhalten - Variadisches Template
-
Hoi,
ich bastle mir gerade einen Wrapper um die Sqlite3 C-API.Ein Teil des Codes:
template<typename> struct ColumnFetcher; template<> struct ColumnFetcher<char const*> { static bool canFetch(Statement& stmnt, int column) { return sqlite3_column_type(stmnt.get(), column) == SQLITE3_TEXT; } static char const* fetch(Statement& stmnt, int column) { assert(canFetch(stmnt, column)); return (char const*)sqlite3_column_text(stmnt.get(), column); } }; template<> struct ColumnFetcher<std::string> { static bool canFetch(Statement& stmnt, int column) { return ColumnFetcher<char const*>::canFetch(stmnt, column); } static std::string fetch(Statement& stmnt, int column) { char const* ptr = ColumnFetcher<char const*>::fetch(stmnt, column); int numBytes = sqlite3_column_bytes(stmnt.get(), column); return std::string(ptr, ptr + numBytes); } }; template<> struct ColumnFetcher<int> { static bool canFetch(Statement& stmnt, int column) { return sqlite3_column_type(stmnt.get(), column) == SQLITE_INTEGER; } static int fetch(Statement& stmnt, int column) { assert(canFetch(stmnt, column)); return sqlite3_column_int(stmnt.get(), column); } }; template<typename... Types> std::tuple<Types...> fetchRow(Statement& stmnt) { int columnIndex = 0; return std::make_tuple(ColumnFetcher<Types>::fetch(stmnt, columnIndex++)...); }Jedenfalls jammert der GCC über das columnIndex++. Wie würde es denn korrekt aussehen? Mir fällt gerade dazu recht wenig ein ...
Danke und Grüße,
Ethon
-
template<typename... Types> std::tuple<Types...> fetchRow(Statement& stmnt) { int columnIndex = 0; return std::tuple<Types...>{ColumnFetcher<Types>::fetch(stmnt, columnIndex++)...}; }Der Aufruf von std::make_tuple wäre ein normaler Funktionsaufruf, und die Auswertung von Funktionsargumenten ist bekanntermaßen ungeordnet.
std::tuple<Types...>(ColumnFetcher<Types>::fetch(stmnt, columnIndex++)...);(runde Klammern)
wäre ebenso ein normaler Funktionsaufruf mit dem gleichen Problem.std::tuple<Types...>{ColumnFetcher<Types>::fetch(stmnt, columnIndex++)...};Führt zwar auch zum Aufruf des Konstruktors, ist aber gleichzeitig eine List-Initialisierung.
8.5.4/4 bestimmt, das hier die syntaktische Form die Auswertungsreihenfolge bestimmt. Die Idee war, dass die Semantik nicht davon abhängen sollte (und also generischer Code sich nicht darum zu kümmern brauch), wie die Initialisierung letztlich durchgeführt wird (per normalem Konstruktor, per initializer_list-Konstruktor oder per Aggregatinitialisierung - die letzten beiden Fälle garantieren diese Ausführungsreihenfolge sowieso).In diesem speziellen Fall könnte man das auch noch anders lösen, indem die Indizes als Templateargument geliefert werden.
template<typename... Types, int... i> std::tuple<Types...> fetchRow_(Statement& stmnt, index_list<i...>) // index_list wurde schon oft gezeigt { return std::make_tuple(ColumnFetcher<Types>::fetch(stmnt, i)...); } template<typename... Types> std::tuple<Types...> fetchRow(Statement& stmnt) { return fetchRow_<Types...>(stmnt, make_index_list<sizeof...(Types)>()); }Auf einer sehr theoretischen Ebene gibt das dem Compiler mehr Freiheit beim Optimieren.
-
Also, mit deiner ersten Lösung warnt der GCC weiterhin und wertet dummerweise auch noch von rechts nach links aus.
Ich versuch's mal mit der zweiten, danke!
-
Ethon schrieb:
Also, mit deiner ersten Lösung warnt der GCC weiterhin
kann ich bestätigen.
und wertet dummerweise auch noch von rechts nach links aus.
das nicht, liegt aber ggf. am jeweiligen Code.
clang warnt nicht.
-
Mit zweiter Lösung klappt es super, vielen Dank dir.

-
Ethon schrieb:
Mit zweiter Lösung klappt es super, vielen Dank dir.

Kannst du mal vollständigen Code posten, der von gcc fehlerhaft umgesetzt wird?
Die o.g. Regel wurde in ihrer finalen Form erst im März 2011 aufgenommen, so dass Versionen vor 4.7 diese im Prinzip nicht unterstützen können.
gcc scheint allerdings auch der einzige Compiler zu sein, der dieses Problem zeigt(e).
-
Klar, hier ist er:
#include <iostream> #include <string> #include <tuple> #include <cassert> #include <sqlite3.h> using namespace std; class Sqlite3Error { public: Sqlite3Error(std::string msg, int errorCode, std::string sqliteErrorMsg) { } }; namespace Sqlite3Detail { sqlite3* openDb(char const* dbFile) { sqlite3* handle = nullptr; int ec = sqlite3_open(dbFile, &handle); if(ec != SQLITE_OK) { throw Sqlite3Error("Failed to open database", ec, sqlite3_errmsg(handle)); } return handle; } sqlite3_stmt* prepareStatement(sqlite3* handle, std::string const& statement) { sqlite3_stmt* prepared = nullptr; char const* unused = nullptr; int ec = sqlite3_prepare_v2(handle, statement.c_str(), statement.size() + 1, &prepared, &unused); if(ec != SQLITE_OK) { throw Sqlite3Error("Failed to compile statement", ec, sqlite3_errmsg(handle)); } return prepared; } } class Handle { private: sqlite3* m_db; void dispose() { if(m_db) sqlite3_close(m_db); } // Forbid copies. Handle(Handle const&) = delete; Handle& operator=(Handle const&) = delete; public: explicit Handle(char const* dbFile) : m_db(Sqlite3Detail::openDb(dbFile)) { } ~Handle() { dispose(); } Handle(Handle&& other) : m_db(other.m_db) { other.m_db = nullptr; } Handle& operator=(Handle&& other) { m_db = other.m_db; other.m_db = nullptr; return *this; } sqlite3* get() { return m_db; } }; class Statement { private: sqlite3_stmt* m_statement; void dispose() { if(m_statement) sqlite3_finalize(m_statement); } // Forbid copies. Statement(Statement const&) = delete; Statement& operator=(Statement const&) = delete; public: Statement(Handle& db, std::string const& statement) : m_statement(Sqlite3Detail::prepareStatement(db.get(), statement)) { } Statement(Statement&& other) : m_statement(other.m_statement) { other.m_statement = nullptr; } Statement& operator=(Statement&& other) { m_statement = other.m_statement; other.m_statement = nullptr; return *this; } sqlite3_stmt* get() { return m_statement; } void reset() { int ec = sqlite3_reset(get()); if(ec != SQLITE_OK) { throw Sqlite3Error("Failed to reset statement", ec, sqlite3_errstr(ec)); } } bool step() { int ec = sqlite3_step(get()); if(ec == SQLITE_ROW) return true; if(ec == SQLITE_DONE) return false; throw Sqlite3Error("Failed to execute statement", ec, sqlite3_errstr(ec)); } }; template<typename> struct ColumnFetcher; template<> struct ColumnFetcher<char const*> { static bool canFetch(Statement& stmnt, int column) { return sqlite3_column_type(stmnt.get(), column) == SQLITE3_TEXT; } static char const* fetch(Statement& stmnt, int column) { return (char const*)sqlite3_column_text(stmnt.get(), column); } }; template<> struct ColumnFetcher<std::string> { static bool canFetch(Statement& stmnt, int column) { return ColumnFetcher<char const*>::canFetch(stmnt, column); } static std::string fetch(Statement& stmnt, int column) { char const* ptr = ColumnFetcher<char const*>::fetch(stmnt, column); int numBytes = sqlite3_column_bytes(stmnt.get(), column); return std::string(ptr, ptr + numBytes); } }; template<> struct ColumnFetcher<int> { static bool canFetch(Statement& stmnt, int column) { return sqlite3_column_type(stmnt.get(), column) == SQLITE_INTEGER; } static int fetch(Statement& stmnt, int column) { return sqlite3_column_int(stmnt.get(), column); } }; template<typename... Types> std::tuple<Types...> fetchRow(Statement& stmnt) { int columnIndex = 0; return std::tuple<Types...>{ColumnFetcher<Types>::fetch(stmnt, columnIndex++)...}; } int main() { Handle handle("Dummy.db"); Statement stmnt(handle, "CREATE TABLE IF NOT EXISTS users (uname TEXT PRIMARY KEY,pass TEXT NOT NULL,activated INTEGER)"); std::cout << "Has data ? " << (stmnt.step() ? "yes" : "no") << std::endl; stmnt.reset(); Statement stmnt2(handle, "INSERT INTO users VALUES ('Flo', 'password', 1)"); std::cout << "Has data ? " << (stmnt2.step() ? "yes" : "no") << std::endl; Statement stmnt3(handle, "SELECT * FROM users"); std::cout << "Has data ? " << (stmnt3.step() ? "yes" : "no") << std::endl; auto row = fetchRow<std::string, std::string, std::string>(stmnt3); std::cout << "uname: " << std::get<0>(row) << std::endl; std::cout << "pass: " << std::get<1>(row) << std::endl; std::cout << "activated: " << std::get<2>(row) << std::endl; std::cout << "Has data ? " << (stmnt3.step() ? "yes" : "no") << std::endl; std::cerr << "End\n"; }Erwartet:
uname: Flo
pass: password
activated: 1Ausgabe:
uname: 1
pass: password
activated: Flo
-
Deine Move-Assignment Operatoren sehen mir allesamt falsch aus. Sie disposen *this nicht.
-
Kellerautomat schrieb:
Deine Move-Assignment Operatoren sehen mir allesamt falsch aus. Sie disposen *this nicht.
Jap, und noch ein paar andere Maccken. Der Code war ja nur zum Rumspielen mit Sqlite da, so benutze icch es aucgh nicht im Projekt.

-
Ich kann das Problem mit 4.7.2 und 4.8 alpha nachvollziehen. Ein gcc-bug existiert.
-
Wow, das erste Mal dass der Compiler schuld ist und nicht ich. :p