98 posts
Templated vectors
6 years, 2 months ago Edited by Flyingsand on Feb. 25, 2015, 1:27 a.m.
In episode 71, Casey mentions that templatizing vectors would be another approach (not one that he would necessarily use) to implement them to avoid copy-pasting and duplicated code. I'm not a big template fan either, but I do use them on occasion when it makes sense, so I decided to templatize the vectors to see how it would come out. In case others are interested in this approach, here is what I came up with:

 ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174``` ```template union vec { real32 e[T]; real32& operator[](int index) { return e[index]; } const real32& operator[](int index) const { return e[index]; } }; template<> union vec<2> { struct { real32 x, y; }; real32 e; }; template<> union vec<3> { struct { real32 x, y, z; }; struct { real32 r, g, b; }; struct { vec<2> xy; real32 pad_; }; real32 e; }; template<> union vec<4> { struct { real32 x, y, z, w; }; struct { real32 r, g, b, a; }; struct { vec<3> xyz; real32 pad_; }; real32 e; }; typedef vec<2> vec2_t; typedef vec<3> vec3_t; typedef vec<4> vec4_t; #if 0 //!!! DANGER !!! template vec Vec(real32 e, ...) { vec result; va_list args; va_start(args, e); result.e = e; for (int i = 1; i < T; ++i) { result.e[i] = (real32)va_arg(args, real64); } va_end(args); return result; } #endif template vec Vec(Args... args) { const int length = sizeof...(args); static_assert(length == T, ""); vec result = {static_cast(args)...}; return result; } template vec operator*(real32 scalar, vec v) { vec result; for (int i = 0; i < T; ++i) { result.e[i] = scalar * v.e[i]; } return result; } template vec operator*(vec v, real32 scalar) { vec result = scalar * v; return result; } template vec& operator*=(vec& v, real32 scalar) { v = scalar * v; return v; } template vec operator-(vec v) { vec result; for (int i = 0; i < T; ++i) { result.e[i] = -v.e[i]; } return result; } template vec operator+(vec A, vec B) { vec result; for (int i = 0; i < T; ++i) { result.e[i] = A.e[i] + B.e[i]; } return result; } template vec& operator+=(vec& A, vec B) { A = A + B; return A; } template vec operator-(vec A, vec B) { vec result; for (int i = 0; i < T; ++i) { result.e[i] = A.e[i] - B.e[i]; } return result; } template vec& operator-=(vec& A, vec B) { A = A - B; return A; } template vec Hadamard(vec A, vec B) { vec result; for (int i = 0; i < T; ++i) { result.e[i] = A.e[i] * B.e[i]; } return result; } template real32 Dot(vec A, vec B) { real32 result = 0.f; for (int i = 0; i < T; ++i) { result += (A.e[i] * B.e[i]); } return result; } template real32 LengthSq(vec v) { real32 result = Dot(v, v); return result; } template real32 Length(vec v) { real32 result = Sqrt(LengthSq(v)); return result; } ```

One notable thing is that by using template specialization, you can in fact use member accesses like .x, .y, .r, .g, etc. That would have been a deal-breaker for me, because writing
 `1` ```velocity.x; ```

is obviously so much more preferable than
 `1` ```velocity.e; ```

or
 `1` ```velocity; ```

I also included the va_args version of the creator function as an alternative (particularly since some compilers may not yet support variadic templates), but it's marked danger for good reason. It doesn't offer any type-safety for the arguments after the first one, so writing
 `1` ```vec<3> aVec = Vec<3>(1.5f, 2.5f, 6); ```

will lead to trouble (i.e. when I tested it, the 6 turned into a 0 so I had {1.5, 2.5, 0}!).

While I haven't tested this, the for loops should be unrolled by any decent compiler since the bounds are known at compile time. All in all, it was pretty straightforward, but in the end I'm not sure this buys us a whole lot. Even though you would just have to add a function once in this version, it is less readable.

In any case, I'm going to keep in my version of HmH because, well.. I took the time to write it. :P

EDIT: I've only tested this using Clang.
poe
4 posts
Templated vectors
2 weeks ago Edited by poe on April 30, 2021, 10:52 p.m.
This is awesome! :)

This really does seem like a best of both worlds solution, where you get the good things you get with templates (not having to write multiple near-identical copies of functions for vec2, vec3, vec4), without sacrificing the wonderous convenience of the anonymous union (v.x instead of the awful v.x() I had been using).

The C-friendly alternative solution I suppose could be to wrap up the vec class into a macro that you then call for 2, 3, 4 to do the code copying, but I prefer templates for this particular problem.