Currently I have a custom vector class as follows:
template<int D, typename T = float>
class Vec
{
private:
T data[D];
public:
Vec(T initial = 0)
{
for (int i = 0; i < D; ++i)
{
data[i] = initial;
}
}
// Misc. operator overloads
};
using Vec2 = Vec<2>;
using Vec3 = Vec<3>;
using Vec4 = Vec<4>;
I would like to add a few member variables, namely x
, y
, z
, and w
, that will point to the first, second, third, and fourth positions in the vector respectively. Ideally, those variables would only be visible if the vector was declared with enough dimensions. I.e. a 2D vector would not have access to Vec<D, T>::z
.
What I have found to work:
template<int D2 = D, typename T2 = typename std::enable_if<(D2 > 0), T*>::type>
T2 x(){ return &data[0];}
template<int D2 = D, typename T2 = typename std::enable_if<(D2 > 1), T*>::type>
T2 y(){ return &data[1];}
template<int D2 = D, typename T2 = typename std::enable_if<(D2 > 2), T*>::type>
T2 z(){ return &data[2];}
template<int D2 = D, typename T2 = typename std::enable_if<(D2 > 3), T*>::type>
T2 w(){ return &data[3];}
As you can see, c++ will have a bit of a midlife crisis if I try to input the template parameters D
and T
as-is so I'm forced to essentially redefine each of them. And I know what I'm about to say is ludicrous but I'd really like for x
, y
, z
, and w
to be variables instead of functions, because in my opinion vec.x
looks nicer than vec.x()
(nitpicky I know).
I can't use this solution because variables can't have templates, only classes and functions. What I'm currently playing with is this:
typename std::conditional<(D > 0), T*, std::nullptr_t>::type x = &data[0];
typename std::conditional<(D > 1), T*, std::nullptr_t>::type y = &data[1];
typename std::conditional<(D > 2), T*, std::nullptr_t>::type z = &data[2];
typename std::conditional<(D > 3), T*, std::nullptr_t>::type w = &data[3];
I had decided to use std::nullptr_t
as the fallback cast because I wanted a type that floats, ints, etc. couldn't be cast to, alas I've come to discover that this is no good. I need a means of preventing x
, y
, z
, and w
from being evaluated until they're called, otherwise any instance of Vec with less than 4 dimensions will throw a compiler error on the line that w
is being evaluated on, meaning regardless of whether I attempt to call w
, it is being evaluated during compilation and will error.
I'm looking for a solution that either prevents a variable from being evaluated unless it is being called (a solution that isn't called a function) or a solution that will alow me to make a member variable completely invisible outside the class under specific circumstances.
Edit: I've had an additional go at it, no success:
private:
T data[D];
auto getW(){return &data[4];};
public:
typename std::conditional<(D > 3), T*, std::nullptr_t>::type w = getW();
This still throws a compilation error for any instance of class Vec
with less than 4 dimesions. Now I'm really confused. The error I get is cannot convert ‘float*’ to ‘std::conditional<false, float*, std::nullptr_t>::type’ {aka ‘std::nullptr_t}
but that doesn't make any sense, getW()
should never be ran, the comparison between float*
and std::nullptr_t
should never be made...?
You need to specialize Vec
to add these variables. Specializing the entire Vec
class would result in a whole lot of duplicated code so you can hoist it into a base class responsible for storing the data of a vector.
template<size_t D, typename T>
class VecStorage {
T data[D];
public:
T &operator[](const size_t i) {
return const_cast<T &>(std::as_const(*this)[i]);
}
const T &operator[](const size_t i) const {
assert(i < D);
return data[i];
}
};
template<typename T>
class VecStorage<2, T> {
public:
T x, y;
T &operator[](const size_t i) {
return const_cast<T &>(std::as_const(*this)[i]);
}
const T &operator[](const size_t i) const {
assert(i < 2);
if (i == 0) {
return x;
} else {
return y;
}
}
};
I've specialized VecStorage
for 2 dimensions. You can add specializations for 3 and 4 dimensions. Since VecStorage
exposes the subscript operator, Vec
and users of Vec
can treat it like an array.
template<size_t D, typename T = float>
class Vec : public VecStorage<D, T> {
public:
explicit Vec(T initial = 0) {
for (size_t i = 0; i != D; ++i) {
(*this)[i] = initial;
}
}
};
int main() {
Vec<2> v{42};
std::cout << v.x << ' ' << v.y << '\n'; // 42 42
v[0] = 5;
v[1] = 7;
std::cout << v.x << ' ' << v.y << '\n'; // 5 7
}