式鋳型

書いては消しを繰り返しているもんだから何も進んでいない。なんかおもろいもんは無かろうか?循環参照も殺せるスマートポインタとか面白そうだが、参照を追跡しようと思ったらクラスのメンバーにあるスマートポインタを追跡する必要があって、こればっかりは自動化できない。これをどれだけナイスな方法でもって解決するかというところか。
で、それはそれとして最近書いたコードと言えば会社で書いてた3次元用vector, matrix templateだが、Expression Templateで実装してみた。本当はuBLASを使いたかったのだが、なんというかやってみたくなったので自作した次第。以下は自分とあと誰かの参考になればいいかも知れないメモ。

とりあえず3次元のベクトルを表現するクラスのインターフェースを決める

  • operator[]で要素取得
  • operator=で右辺のベクトルを丸ごと左辺に格納
  • operator+, operetor-で足したり引いたり
  • dot_prod()でドット積
  • cross_prod()でクロス積

とりあえずはこれぐらいで良かろうか?あと計算するベクトルの要素型が違ってても(vector + vectorとかでも)扱えるようにしたい。


で、私が書いた実装はこんな感じ(正確ではない)。このソースコードでは適切なパラメータ渡しとかはやってない。

// mixin template
template<typename Vec, typename Elem>
struct vector_base {
    typedef Vec real_type;
    typedef Elem element_type;
protected:
    template<typename V>
    void assign(V const & v) {
        real_type& self = static_cast<real_type&>(*this);
        for (unsigned char i = 0; i < 3; i++) {
            self[i] = v[i];
        }
    }
    // その他共通実装など
};

// 内部に配列を保持するvector
template<typename Elem>
struct vector : public vector_base<vector<Elem>, Elem> {
    typedef Elem element_type;
private:
    element_type array_[4];
public:

    element_type & operator[](std::size_t i) {
        return array_[i];
    }

    element_type const & operator[](std::size_t i) const {
        return array_[i];
    }

    template<typename Vec>
    vector& operator=(Vec const& v) {
        assign(v);
        return *this;
    }

    template<typename Vec>
    vector(Vec const & v) {
        assign(v);
    }

    vector(Elem const & x, Elem const & y, Elem const & z) {
        array_[0] = x;
        array_[1] = y;
        array_[2] = z;
        array_[3] = element_type(1);
    }
};

// ベクトル同士の二項演算子ベース
template<typename Expr, typename Lhs, typename Rhs>
struct vector_binary_op : public vector_base<Expr, typename promote_type<typename Lhs::element_type, typename Rhs::element_type>::type> {
    typedef typename promote_type<typename Lhs::element_type, typename Rhs::element_type>::type element_type;
protected:
    Lhs const & lhs_;
    Rhs const & rhs_;
    vector_binary_op(Lhs const & lhs, Rhs const & rhs) : lhs_(lhs), rhs_(rhs) {}
};


// ベクトル加算のクラス
template<typename Lhs, typename Rhs>
struct vector_add : public vector_binary_op<vector_add<Lhs, Rhs>, Lhs, Rhs> {
    typedef vector_binary_op<vector_add<Lhs, Rhs>, Lhs, Rhs> base_type;
    typedef typename base_type::element_type element_type;
    using base_type::lhs_;
    using base_type::rhs_;
public:
    // v[i]みたいに呼ばれたときに計算して返す
    typename base_type::element_type operator[](std::size_t i) const {
        return lhs_[i] + rhs_[i];
    }

    vector_add(Lhs const & lhs, Rhs const & rhs) : base_type(lhs, rhs) {}
};

// operator+
template<class Lhs, typename LElem, class Rhs, typename RElem>
vector_add<Lhs, Rhs> operator+(vector_base<Lhs, LElem> const & lhs, vector_base<Rhs, RElem> const & rhs) {
    return vector_add<Lhs, Rhs>(static_cast<Lhs const &>(lhs), static_cast<Rhs const &>(rhs));
}

// あとなんかoperator-とかの似たような定義
...

int main() {
    using std::cout;
    using std::endl;
    vector<float> vec1(1.0f, 1.0f, 1.0f);
    vector<int> vec2(1.0f, 2.0f, 3.0f);
    vector<float> vec3 = vec1 + vec2;
    vec1 = vec2;

    cout << vec1[0] << endl;
    cout << vec1[1] << endl;
    cout << vec1[2] << endl;

    cout << vec3[0] << endl;
    cout << vec3[1] << endl;
    cout << vec3[2] << endl;

    return 0;
}

promote_typeは二項演算を適用した後の型を特定するテンプレートで、boostのmplとtype traitsを使って実装。
色々削ぎ落として書いたのでv = v1 + v2 + v3...ぐらいのことしかできない。それと、正直4要素ぐらいではETしなくてもいいと思うが気にしてはいけない。目的はあくまでもテンプレートをこねくり回してETすることである。
詳しい説明はまた今度。もう眠い。


えんいー