If you want to make it portable, type-punning is out. I'd use getters:
enum idx : std::size_t { X, Y, Z, W };
template <class T, std::size_t N>
struct Vector {
template <idx I>
constexpr T& get() {
static_assert(I < N, "That dimension does not exist");
return m_data[I];
}
template <idx I>
constexpr T get() const {
return const_cast<Vector<T, N>*>(this)->get<I>();
}
constexpr T& operator[](std::size_t idx) { return m_data[idx]; }
constexpr const T& operator[](std::size_t idx) const { return m_data[idx]; }
T m_data[N]{};
};
Then with a Vector<double, 4> v4;
you could access X
, Y
, Z
and W
like so:
std::cout << v4.get<X>() << ',' << v4.get<Y>() << ','
<< v4.get<Z>() << ',' << v4.get<W>() << '\n';
Demo
A second version could be to inherit from a number of base classes providing the correct accessors
template <class, std::size_t, std::size_t> struct accessors;
template <class T, std::size_t N>
struct accessors<T, N, 1> : std::array<T, N> {
constexpr T& x() { return (*this)[X]; }
constexpr const T& x() const { return (*this)[X]; }
};
template <class T, std::size_t N>
struct accessors<T, N, 2> : accessors<T, N, 1> {
constexpr T& y() { return (*this)[Y]; }
constexpr const T& y() const { return (*this)[Y]; }
};
template <class T, std::size_t N>
struct accessors<T, N, 3> : accessors<T, N, 2> {
constexpr T& z() { return (*this)[Z]; }
constexpr const T& z() const { return (*this)[Z]; }
};
template <class T, std::size_t N>
struct accessors<T, N, 4> : accessors<T, N, 3> {
constexpr T& w() { return (*this)[W]; }
constexpr const T& w() const { return (*this)[W]; }
};
Your Vector
would then be ...
template <class T, std::size_t N>
struct Vector : accessors<T, N, N> {
// ... operator+=, operator-= ...
};
.... and used like this:
int main() {
constexpr Vector<double, 4> v1{1, 2, 3, 4}, v2{5, 6, 7, 8};
constexpr auto v3 = v1 + v2;
std::cout << v3.x() << ',' << v3.y() << ','
<< v3.z() << ',' << v3.w() << '\n';
}
Demo
A third alternative could be to use tagged subscript operator[]
overloads.
struct tag_x {} X;
struct tag_y {} Y;
struct tag_z {} Z;
struct tag_w {} W;
using tags = std::tuple<tag_x, tag_y, tag_z, tag_w>;
template <class T, std::size_t N, std::size_t I>
struct tagged_subscr : tagged_subscr<T, N, I - 1> {
using tagged_subscr<T, N, I - 1>::operator[];
constexpr T& operator[](std::tuple_element_t<I - 1, tags>) {
return (*this)[I - 1];
}
constexpr const T& operator[](std::tuple_element_t<I - 1, tags>) const {
return (*this)[I - 1];
}
};
template <class T, std::size_t N>
struct tagged_subscr<T, N, 1> : std::array<T, N> {
using std::array<T, N>::operator[];
constexpr T& operator[](tag_x) { return (*this)[0]; }
constexpr const T& operator[](tag_x) const { return (*this)[0]; }
};
template <class T, std::size_t N>
struct Vector : tagged_subscr<T, N, N> { };
where the usage then becomes:
int main() {
constexpr Vector<double, 4> v1{1, 2, 3, 4}, v2{5, 6, 7, 8};
constexpr auto v3 = v1 + v2;
std::cout << v3[X] << ',' << v3[Y] << ',' << v3[Z] << ',' << v3[W] << '\n';
}
Demo
I also have a hard restriction on memory, so using reference variables isn't an option
You don't have to store the references in the actual Vector
, but you could create temporary overlays when you need them if you find using v4.x
instead of any of the above (like v4[X]
) a lot more convenient.
template <class, std::size_t> struct overlay;
template <class T>
struct overlay<T, 1> {
template <class V> overlay(V& v) : x{v[X]} {}
T& x;
};
template <class T>
struct overlay<T, 2> : overlay<T, 1> {
template <class V> overlay(V& v) : overlay<T, 1>(v), y{v[Y]} {}
T& y;
};
template <class T>
struct overlay<T, 3> : overlay<T, 2> {
template <class V> overlay(V& v) : overlay<T, 2>(v), z{v[Z]} {}
T& z;
};
template <class T>
struct overlay<T, 4> : overlay<T, 3> {
template <class V> overlay(V& v) : overlay<T, 3>(v), w{v[W]} {}
T& w;
};
// deduction guides:
template <template<class, std::size_t> class C, class T, std::size_t N>
overlay(C<T, N>&) -> overlay<T, N>;
template <template<class, std::size_t> class C, class T, std::size_t N>
overlay(const C<T, N>&) -> overlay<const T, N>;
Which could then be used like so:
int main() {
constexpr Vector<double, 4> v1{1, 2, 3, 4}, v2{5, 6, 7, 8};
constexpr auto v4 = v1 + v2;
overlay o{v4};
std::cout << o.x << ',' << o.y << ',' << o.z << ',' << o.w << '\n';
}
Demo
The overlay could even be used on std::array
s if you want to:
int main() {
std::array<double, 4> a4{1, 2, 3, 4};
overlay o{a4};
std::cout << o.x << ',' << o.y << ',' << o.z << ',' << o.w << '\n';
}