各 vec 型に対する次元数の取得方法


関連:クラスに持たせる定数について
http://d.hatena.ne.jp/gintenlabo/20101214/1292329739


現状の個人的なまとめ。
vec 型の次元数を取得するメタ関数 dimension の実装手段をいくつか。
使い方はこんな感じ。

struct my_vec{
    float x, y, z;
};

BOOST_STATIC_ASSERT(( dimension<my_vec>::value == 3 ));
BOOST_STATIC_ASSERT(( dimension<D3DVECTOR>::value == 3 ));

下記のどれかの方法で定義しておけば使うことが出来るとかそんな感じです。
また、dimension の細かい実装方法については割愛。

1.dimension を特殊化

struct my_vec{
    float x, y, z;
};

template<>
struct dimension<my_vec>
    : public boost::integral_constant<std::size_t, 3>{};

template<>
struct dimension<D3DVECTOR>
    : public boost::integral_constant<std::size_t, 3>{};


BOOST_STATIC_ASSERT(( dimension<my_vec>::value == 3 ));
BOOST_STATIC_ASSERT(( dimension<D3DVECTOR>::value == 3 ));


直接 dimension の特殊化を行い、次元数を定義します。
D3DVECTOR の様に外部のライブラリで定義されているものや、変更が出来ない vec 型を使用する場合に有効です。

2.vec 型の内部で定義

struct my_vec{
    typedef boost::integral_constant<std::size_t, 3> dimension_type;
    float x, y, z;
};

template<typename T>
struct dimension : public T::dimension_type{};

BOOST_STATIC_ASSERT(( dimension<my_vec>::value == 3 ));


vec 型の内部で直接定義を行います。
ユーザ側で定義する vec 型には問題ありませんが、D3DVECTOR の様に変更が出来ない vec 型は定義できません。

3.vec_traits を特殊化して定義

struct my_vec{
    float x, y, z;
};

template<typename T>
struct vec_traits;

template<>
struct vec_traits<my_vec>{
    typedef boost::integral_constant<std::size_t, 3> dimension_type;
};

template<>
struct vec_traits<D3DVECTOR>{
    typedef boost::integral_constant<std::size_t, 3> dimension_type;
};

template<typename T>
struct dimension
    : public vec_traits<T>::dimension_type{};


BOOST_STATIC_ASSERT(( dimension<my_vec>::value == 3 ));
BOOST_STATIC_ASSERT(( dimension<D3DVECTOR>::value == 3 ));


vec_traits の特殊化を行い、次元数の定義を行います。
こちらも、dimension の特殊化と同様に、D3DVECTOR の様に定義済みや変更が出来ない vec 型を使用する場合に有効です。
dimension の特殊化と違う点は、次元数以外(例えば、要素の型や iterator 型など)を複数定義する場合に、vec_traits でまとめて定義する事によって、可読性が上がり、変更がしやすいです。

// こんな感じでまとめて定義する
template<>
struct vec_traits<my_vec>{
    typedef boost::integral_constant<std::size_t, 3> dimension_type;
    typedef float* iterator;    // (適当な)iterator 型や
    typedef float value_type;    // 要素の型
};

まとめ

このように各々の定義を行うことで、dimension メタ関数を使用することが出来ます。
複数定義されている場合は、上から順にプラオリティが高くなります。
(2. と 3. が定義されていれば 2. の方が優先して、使用されます)
基本的には、vec_traits で定義し、vec 型の内部で定義されていれば、そちらを優先して使用するって感じになります。
dimension の特殊化は、vec 型の内部で定義されているものを再定義する場合とかかな。
ここでは、次元数の取得を紹介しましたが、要素の型や iterator 型の取得なんかも同じような形で処理を行います。
と、まぁ現状はひとまずこんな感じですね。


以下、チラシの裏

悪魔の所業

長々と書いてきましたが、そんなものいちいち定義するのはめんどくさい!もっと楽がしたい!
と、お思いのあなた!
楽をする方法が 2 つあります。


1つは、『vec 型のサイズ / 要素の型のサイズ』で計算する方法。

template<typename T>
struct dimension
    : public boost::integral_constant<std::size_t, sizeof(T)/sizeof(float)>{};
// ひとまず要素の型 float は決め打ち

struct my_vec{
    float x, y, z;
};

BOOST_STATIC_ASSERT(( dimension<my_vec>::value == 3 ));
BOOST_STATIC_ASSERT(( dimension<D3DVECTOR>::value == 3 ));

おお、素晴らしい。
もちろん、要素の型はどこかで定義しておく必要がありますが、逆にいえば、要素の型があれば次元数を計算することが出来ます。
まぁもちろん、T 型のサイズが『要素型のサイズ x 次元数』である保障はどこにもないのですごく危険ですが。
(vtable があったらその時点でアウト)


2つ目。
もうちょっと頭を柔らかくして考えます。
つまり、T::x, T::y, T::z の変数が定義されていれば3次元、T::x と T::y だけ定義されていれば2次元としてしまえばいいいんじゃないか?と、いう考え。
いや、ある変数が定義されているかどうかなんてコンパイル時に分かるの?って思いますが、C++0x の decltype を使用すれば割と簡単に実装することが出来ます。
(めんどくさいですが C++03 でも出来ないことはないです)
具体的な実装方法は長くなるので割愛しますが、一応、可能かと思います。
まぁこちらも、変数名が違ったり、private だとアクセスできなかったりと問題はいくつかありますが……。


と、そんな感じで、悪魔の所業を2つ考えてみました。
まぁどちらも現状では現実的ではありませんね。
しかし、vec 型ごとに定義しないで済むというのはかなり魅力的。
DirectX の様にかなりの数の vec 型が定義されている場合は使いたくなってきます。
実際にこれらを使って定義するならばもう少し考える必要がありますねー。

以上、終了!