汎用的なベクトルの 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 への要素へアクセスするラッパーを書けばいいような気がした。
うーん、悩ましい。