汎用的なベクトルの at_c を考える
考えました。
[定義]
template<std::size_t N, typename T> typename T::value_type at_c(T const&); struct vec{ typedef float value_type; float x, y, z; }; vec v = {1.0f, 2.0f, 3.0f}; at_c<0>(v); // 1.0f at_c<1>(v); // 2.0f at_c<2>(v); // 3.0f
使い方はこんな感じ。
[at_c_traits を定義する]
at_c_traits を特殊化し、各型の at_c を実装する。
// 特殊化を行い at_c の実装を記述する template<std::size_t N, typename T> struct at_c_traits; // ac_c_traits を呼び出す template<std::size_t N, typename T> auto at_c(T const& v) ->decltype(at_c_traits<N, T>()(v)){ return at_c_traits<N, T>()(v); } // ユーザが定義したベクトル型 struct vec{ typedef float value_type; value_type x, y, z; }; // at_c<0, vec> の実装 template<> struct at_c_traits<0, vec>{ vec::value_type operator ()(vec const& v) const{ return v.x; } }; vec v = {1.0f, 2.0f, 3.0f}; at_c<0>(v); // 1.0f at_c<1>(v); // at_c_traits を特殊化していないので error
auto + decltype で戻り値型の型推論を行なっている。
at_c_traits の特殊化が行われていなければコンパイルエラーになる。
[メンバを直接参照する]
大抵のベクトル型であれば、x, y, z の様なプロパティ持っている(はず)ので、直接参照する方法を考えてみる。
#include <utility> // 特殊化を行い at_c の実装を記述する template<std::size_t N, typename T> struct at_c_traits; // ac_c_traits を呼び出す template<std::size_t N, typename T> auto at_c(T const& v) ->decltype(at_c_traits<N, T>()(v)){ return at_c_traits<N, T>()(v); } // N == 1 の場合、y 要素を参照する template< std::size_t N, typename T, typename SFINAE = typename std::enable_if<N == 1>::type > auto at_c(T const& v) ->decltype(v.y){ return v.y; } // ユーザが定義したベクトル型 struct vec{ typedef float value_type; value_type x, y, z; }; // at_c<0, vec> の実装 template<> struct at_c_traits<0, vec>{ vec::value_type operator ()(vec const& v) const{ return v.x; } }; vec v = {1.0f, 2.0f, 3.0f}; at_c<0>(v); // at_c_traits を参照 at_c<1>(v); // at_c<1, vec> を参照
N == 1(at_c<1>)の場合は、y 要素を参照する。
at_c_traits を特殊化していなくても T 型が y を持っていれば参照することができる。
[at_c_traits と特殊化がされている場合]
at_c_traits と特殊化の両方が実装されている場合、関数が特定できずに呼び出しに失敗する。
// 特殊化を行い at_c の実装を記述する template<std::size_t N, typename T> struct at_c_traits; // ac_c_traits を呼び出す template<std::size_t N, typename T> auto at_c(T const& v) ->decltype(at_c_traits<N, T>()(v)){ return at_c_traits<N, T>()(v); } // x 要素を参照する template< std::size_t N, typename T, typename SFINAE = typename std::enable_if<N == 0>::type > auto at_c(T const& v) ->decltype(v.x){ return v.x; } // ユーザで定義したベクトル型 struct vec{ typedef float value_type; value_type x, y, z; }; // at_c<0, vec> の実装 template<> struct at_c_traits<0, vec>{ vec::value_type operator ()(vec const& v) const{ return v.x; } }; vec v = {1.0f, 2.0f, 3.0f}; at_c<0>(v); // error: call of overloaded 'at_c(vec&)' is ambiguous
と、いう事で以前つくったこれで何とかしてみる。
#include <utility> // ac_c_traits を呼び出す template<std::size_t N, typename T> struct at_c_traits; namespace detail{ template<int N> struct overload_priority : overload_priority<N + 1>{}; template<> struct overload_priority<32>{}; static overload_priority<0> const* overload = NULL; template<typename Pred, int Priority> struct enable_if_priority : std::enable_if<Pred::value, overload_priority<Priority> const*>{}; template<bool Pred, int Priority> struct enable_if_c_priority : std::enable_if<Pred, overload_priority<Priority> const*>{}; typedef enable_if_c_priority<true, 32>::type const* other; // ac_c_traits が定義されていれば、優先的に呼び出す template<std::size_t N, typename T> auto at_c(T const& v, typename enable_if_c_priority<true, 0>::type) ->decltype(at_c_traits<N, T>()(v)){ return at_c_traits<N, T>()(v); } // それ以外 // x 要素を参照する template<std::size_t N, typename T> auto at_c(T const& v, typename enable_if_c_priority<N == 0, 1>::type) ->decltype(v.x){ return v.x; } // y 要素を参照する template<std::size_t N, typename T> auto at_c(T const& v, typename enable_if_c_priority<N == 1, 1>::type) ->decltype(v.y){ return v.y; } // z 要素を参照する template<std::size_t N, typename T> auto at_c(T const& v, typename enable_if_c_priority<N == 2, 1>::type) ->decltype(v.z){ return v.z; } } // namespace detail template<std::size_t N, typename T> auto at_c(T const& v) ->decltype(detail::at_c<N>(v, detail::overload)){ return detail::at_c<N>(v, detail::overload); } // ユーザで定義したベクトル型 struct vec{ typedef float value_type; value_type x, y, z; }; // at_c<0, vec> の実装 template<> struct at_c_traits<0, vec>{ vec::value_type operator ()(vec const& v) const{ return v.x; } }; vec v = {1.0f, 2.0f, 3.0f}; at_c<0>(v); // at_c_traits を特殊化 at_c<1>(v); // y と z は at_c_traits を特殊化してなくても at_c<2>(v); // 要素にアクセスできる
こんな感じで、at_c_traits が特殊化されていればそちらを優先し、それ以外であれば、x, y, z の要素へアクセスを行う。
[まとめ]
と、こんな感じの事を考えたりしています。
まあぶっちゃけ、優先順位とかつけるとごちゃごちゃするので Boost.Fusion へアダプトして、そっちから参照する方がすっきりすると思う。
ここら辺の設計は、Concept(vector_concept) + アダプタ(Boost.Fusion)を使うのがいいんじゃないかなーと最近考えてる(拡張用として、traits はそのままの形でもいいけども。
やはり Concept 的なものは欲しいですね。
いま思ったけど x, y, z への要素へアクセスするラッパーを書けばいいような気がした。
うーん、悩ましい。