式テンプレートについて(テンプレート導入)
class Vect { /* snip */ template<class T> Vect& operator=(const T& rhs) { rhs.eval(*this); return *this; } };
上記の追加により, (あまりよろしくないけど)何かよく分からない型を受け付けて, それに対して eval を実行して *this を得ることができます.
VaddV, VaddV_addV の方は, 一般的に Exp (expression) という名前のテンプレートにしてみましょう.
template<class L> class Exp { const L& lhs_; const Vect& rhs_; public: Exp(const L& lhs, const Vect& rhs) : lhs_(lhs) , rhs_(rhs) { } void eval(Vect& dest) const { dest = lhs_; /* lhs_.eval(dest);と等価 */ Vect::add(rhs_, dest); } }; template<class L> Exp<L> operator+(const L& lhs, const Vect& rhs) { return Exp<L>(lhs, rhs); }
中身は型が変わった以外, 前回の VaddV_addV とほぼ同じです. この変更により, w = x + y; も w = x + y + z; も w = x + x + x + ... + y; にも対応しました. コンパイラがコンパイル時に必要なだけ関数を生成してくれるわけです. 素晴らしい.
それは上記一般化において L = Vect のとき, eval の中で不要な dest = lhs_; が実行されるからです. ここは単に Vect::add(rhs_, lhs_, dest); でよいはず. そのために, L = Vectの時のeval() の特別バージョンを追加します.
template<> void Exp<Vect>::eval(Vect& dest) const { Vect::add(lhs_, rhs_, dest); }
これにより, 無事コンストラクタやコピーが一度も発生しないようにできました(et3.h).
このエラーは右辺の型が Vect しか受け付けないことに起因しています. これを解消するには両辺共に自由な型にしないといけません. というわけで, Exp が二つの方を持てるように
template<class L, class R> class Exp { const L& lhs_; const R& rhs_; public: Exp(const L& lhs, const R& rhs) : lhs_(lhs) , rhs_(rhs) { } void eval(Vect& dest) const { Vect tmpl(lhs_); Vect tmpr(rhs_); Vect::add(tmpl, tmpr, dest); } };
とします. operator+() も同様に
template<class L, class R> Exp<L, R> operator+(const L& lhs, const R& rhs) { return Exp<L, R>(lhs, rhs); }
とします. それから一時変数を作らせないために特殊化を用意します. 右側だけが Vect, 左側だけが Vect, 両方 Vect の三つを作ります. eval() の中身から, それぞれの特殊化に合わせて不要な一時変数を削除します.
それから最後に Vect w(x + y); というコードも処理できるように Vect にコンストラクタを追加しましょう.
template<class L, class R> Vect(const Exp<L, R>& rhs) { rhs.eval(*this); }
これらの修正により
w = x + (y + z) + x + y + z + x + (y + z) + x;
のような複雑(?)な式も処理できるようになりました(et4.h). 残念ながら, このコードはVC6/bcc32ではコンパイルできません. VC7.1またはgcc 3.2以上でないと駄目です.
今まではベクトルごとに足すということをしていました. つまり,
┌ ┐ ┌ ┐ ┌ ┐ ┌ ┐ |w[0]| |x[0]| |y[0]| |z[0]| |w[1]| |x[1]| |y[1]| |z[1]| |w[2]| =(((|x[2]| + |y[2]|) + ...) + ...) + |z[2]| |... | |... | |... | |... | |w[N]| |x[N]| |y[N]| |z[N]| └ ┘ └ ┘ └ ┘ └ ┘
としているわけですが, これを成分ごとに足してみるわけです.
w[0] = (((x[0] + y[0]) + ...) + ...) + z[0] w[1] = (((x[1] + y[1]) + ...) + ...) + z[1] w[2] = (((x[2] + y[2]) + ...) + ...) + z[2] ... w[N] = (((x[N] + y[N]) + ...) + ...) + z[N]
とするのです. 今までは dest を何度もなめる必要がありましたが, こうすると dest をなめるのは一度だけですむのでメモリアクセスコストを大幅に減らせます. eval() を i 番目の成分のみ得るようにし, Vect 本体で それらの eval を 0 から N-1 まで評価するように変更します.
Vect 側
class Vect { /* snip */ template<class L, class R> Vect& operator=(const Exp<L, R>& rhs) { eval(rhs); return *this; } template<class L, class R> Vect(const Exp<L, R>& rhs) { eval(rhs); } int eval(int i) const { return p_[i]; } private: template<class L, class R> void eval(const Exp<L, R>& rhs) { for (int i = 0; i < N; i++) { p_[i] = rhs.eval(i); } } };
template<class L, class R> class Exp { const L& lhs_; const R& rhs_; public: Exp(const L& lhs, const R& rhs) : lhs_(lhs) , rhs_(rhs) { } int eval(int i) const { return lhs_.eval(i) + rhs_.eval(i); } };
ただ int eval(int i) というのは結局は operator[] と同じなので上記 eval() を operator[] に置き換えるとスマートです(vect1.h).
int operator[](int i) const { return lhs_[i] + rhs_[i]; }
今回の場合, 今までと違って Exp の eval(operator[]) の中で一時変数を必要としないため, 特殊化を用意する必要はありません. だからVC6でもコンパイルできます.
template<class L, class Ope, class R> class Exp { const L& lhs_; const R& rhs_; public: Exp(const L& lhs, const R& rhs) : lhs_(lhs) , rhs_(rhs) { } int operator[](int i) const { return Ope::eval(lhs_[i], rhs_[i]); } };
Ope にいれるべきクラスを作ります.
struct ExpAdd { static int eval(int lhs, int rhs) { return lhs + rhs; } }; struct ExpMul { static int eval(int lhs, int rhs) { return lhs * rhs; } };
んで, 演算子を定義します.
template<class L, class R> Exp<L, ExpAdd, R> operator+(const L& lhs, const R& rhs) { return Exp<L, ExpAdd, R>(lhs, rhs); } template<class L, class R> Exp<L, ExpMul, R> operator*(const L& lhs, const R& rhs) { return Exp<L, ExpMul, R>(lhs, rhs); }
これで w = (x + y) * z; も計算できるようになりました.
ちなみにVC7.1でコンパイルすると N = 4 なら全部計算してしまうようで(DEBUGオフ時),
アセンブリ言語出力を見ると w に直接定数値が代入されていました.
x, y, zは影も形もありません(んなアホな).
; 44 : w = x * (y + z); ; 45 : w = w * x; ; 50 : } ; 52 : cout << "w=" << w << endl; 00005 8d 44 24 08 lea eax, DWORD PTR _w$[esp+24] 00009 50 push eax 0000a 68 00 00 00 00 push OFFSET FLAT:$SG9889 0000f 68 00 00 00 00 push std::cout 00 00 00 mov DWORD PTR _w$[esp+36], 7 00 00 00 mov DWORD PTR _w$[esp+40], 32 ; 00000020H 00 00 00 mov DWORD PTR _w$[esp+44], 81 ; 00000051H 00 00 00 mov DWORD PTR _w$[esp+48], 160 ; 000000a0H 00034 e8 00 00 00 00 call std::operator<<<std::char_traits<char> >
より最適化が難しいと思われる et4.h ですら w = x + (y + z); を計算完了しています. w = x * (y + z) * x; は流石にしなかったけど, w = x * (y + z); w = w * x; は計算完了してました. 恐るべし.
ついでに右からのスカラー倍も作っておきましょう. R = int の特殊化を追加すればOKです(vect2.h).
template<class L> class Exp<L, ExpMul, int> { const L& lhs_; const int rhs_; public: Exp(const L& lhs, int rhs) : lhs_(lhs) , rhs_(rhs) { } int operator[](int i) const { return lhs_[i] * rhs_; } };
w = (x + y) * z * 3;
続く.