stateless-lambdaのオイシイところ
stateless-lambdaが関数ポインタに変換できるとナニがオイシイか、ですか? 言うまでもなく「コールバック関数をその場に書き下せる」ことでしょう。たとえばおなじみqsort。qsortはその引数に比較関数のポインタを与えなくてなりません。従来なら:
int compare(const void* x, const void* y) {
return *static_cast<const int*>(x) - *static_cast<const int*>(y);
}
...
array<int,10> iary = { 9, 8, 7, 6, 5, 0, 1, 2, 3, 4 };
qsort(iary.data(), iary.size(), sizeof(int), &compare);
と書いてたのが:
array<int,10> iary = { 9, 8, 7, 6, 5, 0, 1, 2, 3, 4 };
qsort(iary.data(), iary.size(), sizeof(int),
[](const void* x, const void* y) {
return *static_cast<const int*>(x) - *static_cast<const int*>(y);
});
のように、比較関数をlambdaでその場に書き下せます。トップレベルウィンドウを列挙するWin-API:EnumWindowsもこんなカンジで使えます:
// トップレベルウィンドウのタイトルを出力する
EnumWindows(
[](HWND hWnd, LPARAM lParam) -> BOOL {
array<char,128> title;
GetWindowTextA(hWnd, &title[0], title.size());
if ( title[0] != '\0' ) cout << title.data() << '\t';
return TRUE;
}, 0);
ここに挙げたふたつの例は呼び出し規約が異なる(cdeclとstdcall)けども、vc11はどちらも適切に変換してくれます。
応用例:sqlite3_exec16
応用例をひとつ紹介、軽量データベースSQLiteです。SQLiteのC-APIにsqlite3_execという「SQL一発実行おちゃらか関数」が提供されています。食わせたSQL文(文字列)にしたがってデータベースを操作してくれますが、「SELECT ...」によるクエリの結果はコールバック関数で捕まえます。こんなカンジ:
#include <sqlite3.h> // SQLite3
#include <iostream> // cout, endl
#include <cassert> // assert
using namespace std;
int print_records(void* ,int n, char** vals, char** cols) {
for ( int i = 0; i < n; ++i ) {
cout << cols[i] << "=["
<< (vals[i] ? vals[i] : "(null)") << "]\n";
}
cout << endl;
return 0;
}
int main() {
sqlite3* db;
char* errmsg;
int rc;
// データベースのオープン
rc = sqlite3_open(":memory:", &db);
assert( rc == SQLITE_OK );
rc = sqlite3_exec(db,
"CREATE TABLE fruit ( name TEXT, price INTEGER);"
"INSERT INTO fruit VALUES ( 'apple' , 100 );"
"INSERT INTO fruit VALUES ( 'banana', 120 );"
"INSERT INTO fruit VALUES ( 'cherry', 140 );"
"INSERT INTO fruit VALUES ( 'durian', NULL );",
NULL, 0, NULL);
assert( rc == SQLITE_OK );
// "SELECT..." の結果はコールバックで捕まえる
rc = sqlite3_exec(db,
"SELECT * FROM fruit WHERE name > 'c';",
&print_records, &cout, &errmsg);
if ( rc != SQLITE_OK ) {
cout << "SQLite err : " << errmsg << endl;
sqlite3_free(errmsg);
}
sqlite3_close(db);
}
もちろんこいつもlambda使って:
...
rc = sqlite3_exec(db,
"SELECT * FROM fruit WHERE name > 'c';",
[](void* ,int n, char** vals, char** cols) {
for ( int i = 0; i < n; ++i ) {
cout << cols[i] << "=["
<< (vals[i] ? vals[i] : "(null)") << "]\n";
}
cout << endl;
return 0;
},
nullptr, &errmsg);
...
お気軽にデータベースが扱えるお気に入りのAPIなんですが、気に入らないのはsqlite3_execが扱える文字コードがUTF-8だけで、Unicode(wchar_t)をサポートしてくれていません。それじゃってんでこしらえてみました。
ヘッダsqlite3_ex.hにUnicode版sqlite3_exec16を用意します。C++用インタフェースには引数の省略を許し、実行エラー時に例外を投げるsqlite3_exec_ex/sqlite3_exec16_exをオマケに付けちゃいます。
#ifndef SQLITE3_EX_H_
#define SQLITE3_EX_H_
#include <sqlite3.h>
#ifdef __cplusplus
#include <stdexcept> // runtime_error
extern "C" {
#endif
int sqlite3_exec16(
sqlite3* db,
const wchar_t* sql,
int (*callback)(void*,int,wchar_t**,wchar_t**),
void* arg,
char** errmsg
);
#ifdef __cplusplus
}
class sqlite_error : public std::runtime_error {
int code_;
public:
sqlite_error(int c, const std::string& msg)
: std::runtime_error(msg), code_(c) {}
int code() const { return code_; }
};
int sqlite3_exec16_ex(
sqlite3* db,
const wchar_t* sql,
int (*callback)(void*,int,wchar_t**,wchar_t**) =nullptr,
void* arg =nullptr
);
int sqlite3_exec_ex(
sqlite3* db,
const char* sql,
int (*callback)(void*,int,char**,char**) =nullptr,
void* arg =nullptr
);
#endif
#endif
