幾何のベクトルクラスを改めて開発してみた。
- 演算子のオーバーロード (線形計算)
- 固定次元(テンプレート)
- クロス積(2次元・3次元)
ここで、
- 次元に関わらず共通処理(線形計算、ドット積、ノルム、要素アクセス、etc.)
- 次元によって異なる処理(クロス積)
の実装が問題となる。
「共通する部分を親クラスに実装し、次元ごとに派生クラスを作る」が一般的な手法であるが、
例えば下記のようなコードにおいて冗長な記述が必要となる。
class Vec2D : public Vec_<double, 2>
{
public:
// 親クラスを受け入れるコンストラクタが必要
Vec2D(Vec_<double,2>& v) : Vec_<double,2>(v) {};
double cross(Vec2D const& v);
};
class Vec3D : public Vec_<double, 3>
{
public:
Vec3D(Vec_<double,3>& v) : Vec_<double,3>(v) {};
Vec3D cross(Vec3D const& v);
};
Vec2D v1(3,5), v2(5,6), v3(1,2);
//double result = (v1+v2).cross(v3); // error: operator+の戻り値はVec_<T,N>
double result = Vec2D(v1+v2).cross(v3);
Vec3D v4(3,5,1), v5(5,6,2), v6(1,2,3);
Vec3D v7 = Vec3D(v4+v5).cross(v6);
派生クラスに次元に特化したメンバー関数を定義しても、親クラスで定義した共通部分の結果は当然親クラスで返される。
このような場合、テンプレートの特殊化で対応すれば派生クラスを作る必要はなく、上記冗長性も生じない。
まず思いつくのは
template<typename T, size_t N>
class Vec_
{
public:
template<typename T2>
T2 cross(Vec_ const& v);
};
しかし、特殊化部分を記述しようとするとつまずく。
/* ill-form
template<T>
template<>
T Vec_<T,2>::cross<T>(Vec_<T,2> const& v) { }
*/
関数テンプレートは部分特殊化できない。また戻り値をどう記述してよいかわからない。
このような場合固有処理を外部クラスに定義すればよい。
template<typename VectorType>
struct compute_cross
{
typedef void return_type; // クロス積は2次元と3次元のみ
};
template<typename T>
struct compute_cross<Vec_<T,2>>
{
typedef T return_type;
return_type apply(Vec_<T,2> const& v1, Vec_<T,2> const& v2) { // }
};
template<typename T>
struct compute_cross<Vec_<T,3>>
{
typedef Vec_<T,3> return_type;
static inline return_type apply(Vec_<T,3> const& v1, Vec_<T,3> const& v2) { // }
};
template<typename T, size_t N>
class Vec_
{
public:
compute_cross<Vec_>::return_type cross(Vec_ const& v) const
{
return compute_cross<Vec_>::apply(*this, v);
}
};
また、2次元と3次元以外にはcompute_cross::applyが定義されていないが、Vec_::crossを呼び出さない限りエラーとはならない。
typedef Vec_<double,3> Vec3D;
typedef Vec_<double, 4> Vec4D;
Vec3D v1, v2;
Vec4D v3, v4; // ok
std::fill(v1.begin(), v1.end(), 1); // v1(1,1,1)
std::fill(v2.begin(), v2.end(), 2); // v2(2,2,2)
std::fill(v3.begin(), v3.end(), 1); // v3(1,1,1,1)
std::fill(v4.begin(), v4.end(), 2); // v4(2,2,2,2)
v1+v2; // ok
v1.dot(v2); // ok
v1.cross(v2); // ok
v3+v4; // ok
v3.dor(v4); // ok
v3.cross(v4); // error: compute_cross<Vec_<double,4>>::applyがない