テンプレートとかイテレータとかファンクタとか
ARシステム作ったりしたからそれに関する記事書いてくぜと言うだけ言って一向に実行する気配がありませんが、今後もないわけですが(オイ)、PC上でもiOS上でも動くようにしたいなぁという要求から、システムのコアな部分はC++で書くべきなのかなという結論に至りそうな今日この頃です*1。
というわけで、久しぶりのC++なわけですが、あまりに重苦しいクラスゴリゴリプログラミングは嫌なので、とりまテンプレート周りの復習と、関数型的プログラミング手法を整理してみました。
テンプレートの基本
とりあえずサンプルコード。
#include <iostream> template <class T> T identity(T val) { return val; } template <class T> class Adder { T val_; public: Adder(T val): val_(val) {} T add(T val) { return val_ + val; } }; int main() { std::cout << identity<const char*>("foo") << std::endl; std::cout << identity("bar") << std::endl; std::cout << identity(100) << std::endl; std::cout << Adder<int>(10).add(20) << std::endl; std::cout << Adder<double>(12.3).add(45.6) << std::endl; return 0; }
template <class T>
はtemplate <typename T>
と全くの同義です。
コンパイラが型推論が出来る場合は型指定が要らないのですが、クラスに関しては必要なようです。
テンプレートとイテレータでコンテナを統一的に走査する
C++はコンパイラ主義というか、オブジェクト主義なJavaやObjective-C、ゆとりなLLとは違う感覚が必要ですね。
以下のサンプルコードですが、これだけのことを書くのに随分時間がかかってしまいました。結局algorithmヘッダを参考にしたのですが、Web上にはこの手のノウハウの情報が少ない気がします。
#include <iostream> #include <vector> #include <list> template <class InputIterator> void print(InputIterator begin, InputIterator end) { for (InputIterator it = begin; it != end; ++it) std::cout << *it << " "; std::cout << std::endl; } int main() { std::vector<int> aVector; for (int i = 0; i < 10; ++i) aVector.push_back(i); std::list<int> aList(aVector.begin(), aVector.end()); print(aVector.begin(), aVector.end()); print(aList.begin(), aList.end()); print(static_cast<std::vector<int>::const_iterator>(aVector.begin()), static_cast<std::vector<int>::const_iterator>(aVector.end())); print(aList.cbegin(), aList.cend()); return 0; }
テンプレートでイテレータの型を汎化させておけば、コンテナを問わず同じインタフェースで取り扱えます。
保守性を高めるためにイテレータが書き換え可能かを指定したい時は、テンプレート関数側でそういう制約を設けるのではなく、渡す側でconst_iteratorに型変換するのがC++的な考え方だと思います。もし関数内で書き換えがあった場合は、コンパイラがエラーを吐いてくれる仕組みです。
また、C++0xからは、const_iteratorを返すcbegin/cendメソッドがコンテナに追加されたのでstatic_cast<std::list<int>::const_iterator>(aList.begin())
というグロい書き方をaList.cbegin()
とスッキリ書けるようになったそうです*2。
ファンクタ…あぁ関数オブジェクトのことね
ファンクタと聞くと、Haskellを思い出したり、圏論での関手が云々とか、思わず拒否反応が出るのですが、C++でいうファンクタはそういう恐ろしい話ではなく関数オブジェクトのことを指すそうです*3。
関数オブジェクト - Wikipedia関数オブジェクト
関数オブジェクト(かんすうオブジェクト、英: function object)は、プログラミング言語において、関数(サブルーチンないしプロシージャ)を、オブジェクトとしたものである。手続きオブジェクトとも言う(プロシージャ=手続き)。なお、ここでのオブジェクトの語は、いわゆるオブジェクト指向のそれに限らず、「第一級オブジェクト」という語におけるのと同じ、メモリ上に空間を確保されたもの、といった意味である。関数が第一級オブジェクトである場合は特に第一級関数と言う。
関数と変数の名前空間が共通である言語の場合、構文の設計によっては、a = hoge(b) といったような、通常のサブルーチン呼び出しと全く同じ構文で、関数オブジェクトが意味するサブルーチンを呼び出せる言語もある。
また、変数束縛が閉じられた関数オブジェクトはクロージャである。C#などには関数オブジェクトのようなものとして、オブジェクトのインスタンスとそのメソッドである手続きとを結びつけている、デリゲートがある。無名関数も参照。(中略)
C++のファンクタとファンクショノイド
以上のように C++ では、関数オブジェクトは、「関数呼び出しと同じ構文で、メンバ関数を呼ぶことができるオブジェクト」として実装されている。これを C++ の用語ではファンクタと呼んでいるが、これはStandard MLのfunctorや、数学における関手とは関係ない(と考えたほうが良い)。
C++ では、主要なメソッド一つを持つオブジェクトを「ファンクショノイド」と言い、その一種で、その主要なメソッドが operator() であるオブジェクトが「ファンクタ」である、と説明される[1]。これは C++ の用語であり、C++ を離れた文脈では、関数オブジェクトすなわちファンクタ、ではないので注意。
さすがWikipedia、素晴らしい説明ですね。
というわけでサンプルです。古い環境では、boostが必要かもしれません。
#include <iostream> #include <functional> #include <vector> #include <list> template <class T, class InputIterator> T pipeline(InputIterator begin, InputIterator end, T initial) { T arg = initial; for (InputIterator it = begin; it != end; ++it) arg = (*it)(arg); return arg; } class Adder { int base_; public: Adder(int base): base_(base) {} int operator()(int i) { return base_ + i; } }; std::function<int(int)> increment = Adder(1); int square(int i) { return i * i; } int main() { std::function<int(int)> funcs[] = { increment, square, Adder(0) }; std::cout << pipeline(funcs, &funcs[2], 99) << std::endl; std::vector<std::function<int(int)> > funcArray(funcs, &funcs[2]); std::cout << pipeline(funcArray.begin(), funcArray.end(), 99) << std::endl; std::list<std::function<int(int)> > funcList(funcs, &funcs[2]); std::cout << pipeline(funcList.begin(), funcList.end(), 99) << std::endl; return 0; }
以下、コードの適当な説明です。
- Adderクラス
- ()演算子をオーバーライドすればファンクタになる。
- increment
- 型宣言はfunctionalヘッダの機能を使って、関数ポインタ型から脱却できる。
- main関数
- 1行目:Adder(0)は配列範囲外のアクセス防止のために入れている。もっといい方法あるのかな。
- 2行目:ポインタはイテレータの上位互換なので、こういう書き方もできる。
このぐらいの関数が自由になると、C++使ってみてもいいかなぁという気にもなりますね。