C++ 可変長引数テンプレートのお話
前回のソースコードの中で1点だけ、C++11らしい機能の紹介を。
C++11の機能という点ではgl/InputConstants.h内でenum classを使ってはいますが、基本的なことしかしていませんので省略します。
●可変長引数テンプレート
glut/Window.hに1ヵ所だけ、テンプレートメンバ関数が含まれていることにお気づきでしょうか?
glut/Window.h
/** * コールバックを各々のウィンドウの指定メソッドに割り当てる処理。 */ template <class ... TParams> static void execute(void (Window::*method)(gl::Context &, TParams ...), TParams ... parameters) ;
実装部分は次の通りです:
glut/Window.cpp
template <class ... TParams> void glut::Window::execute(void (Window::*method)(gl::Context &, TParams ...), TParams ... parameters) { // 現在のウィンドウIDからウィンドウを取得 auto it = m_id_map.find(glutGetWindow()) ; if (it == m_id_map.end()) return ; // ウィンドウに対して、指定したメソッドを呼び出す (it->second->*method)(it->second->m_context, parameters ...) ; }
本来であれば、テンプレート関数の定義はヘッダ内で行うものですが(テンプレート関数の定義は翻訳単位ごとに必要になるため)、どうせglut/Window.cpp内部でしか使わないのでglut/Window.cpp内に入れてあります。
この関数のテンプレート宣言には、
これがC++11で2番目に強力(当社調べ)な新機能で、可変長引数テンプレートと呼ばれる機能です(なお、1番はラムダで、3番はムーブセマンティクスだと思っていますが異論は認めます)。
可変長引数テンプレートはtemplate<型 ... 値>の形で宣言し、unpack演算子...を使う事で、式をテンプレートで渡した値の数だけ,区切りにして渡してくれます。
例えばこんな感じです:
template <class ... T> std::tuple<const T*...> hoge(const T& ... x) { return std::make_tuple(&x + 1 ...) ; }
上記のコードでは、unpack演算子を3回使っていますが、正常にコンパイルできます。
関数の場合、テンプレート引数が関数の引数から求まる場合はテンプレート引数の指定を省略できるので、次のようなコードでは、各hogeはコメントのような処理に展開されます。
int x[] = {0, 1} ; double y[] = {0.0, 5.0} ; auto tuple0 = hoge() ; // std::tuple<> hoge<>() { return std::make_tuple() ; } auto tuple2 = hoge(x[0], y[0]) ; // std::tuple<const int*, const double*> hoge<int, double>(const int& x0, const double& x1) { return std::make_tuple(&x0 + 1, &x1 + 1) ; } assert(1 == *std::get<0>(tuple2)) ; assert(5.0 == *std::get<1>(tuple2)) ;
単純にTをカンマ区切りにするわけではなく、Tを含む式を丸ごとコピーしてくれているのがわかります。
glut/Window.h
template <class ... TParams> static void execute(void (Window::*method)(gl::Context &, TParams ...), TParams ... parameters) ;
execute()の第一引数は、gl::Contextへの参照を第一引数とし、第二引数以降はテンプレートで指定した通りの型を持つ、Windowクラスのメンバ関数です。
また、execute()の第二引数以降は、テンプレートで指定した型の値です。
ではexecute()の中身をもう一度見ていきます。
glut/Window.cpp
template <class ... TParams> void glut::Window::execute(void (Window::*method)(gl::Context &, TParams ...), TParams ... parameters) { // 現在のウィンドウIDからウィンドウを取得 auto it = m_id_map.find(glutGetWindow()) ; if (it == m_id_map.end()) return ; // ウィンドウに対して、指定したメソッドを呼び出す (it->second->*method)(it->second->m_context, parameters ...) ; }
前半部は、ウィンドウIDからglut::Windowへのポインタを取得しているだけのコードです。
後半部では、見つかったウィンドウに対してメンバ関数を呼びだしています(C++無数の鬼門の1つと言われるアレです)。この際、execute()に渡した引数をそのまま引数に渡してやっています。
その結果、glutに登録したstaticなコールバック関数内からウィンドウIDに応じたオブジェクトのメンバー関数を呼び出すための処理が、次のように明瞭簡潔に書けることになります。
glut/Window.cpp
void glut::Window::displayFunc() { execute(&Window::display) ; } void glut::Window::reshapeFunc(int width, int height) { execute(&Window::reshape, width, height) ; }
●余談
Web上を見ても日本語の情報がほとんど見つかりませんでしたが、実は、可変長引数テンプレートはclass/typenameだけでなく、整数型にも使えるようです。
// 指定した値を(値の数)倍し、それらの合計を求める template <int ... N> int hoge() { int s = 0 ; for (int x : std::initializer_list<int>({N * sizeof...(N) ...})) { s += x ; } return s ; }
ここで、for (int x : {N * sizeof...(N) ...})とするとテンプレート引数の数が0だった場合に「型が不明」と言われるので、明示的にinitializer_listでくるんでいます。
まあ、整数型を可変長にしたいケースが世の中にどれだけあるかは疑問ですが、一応参考までに。