2

I have the following vector class (vector as in spatial, not array):

template<typename T0, size_t S, typename = typename std::enable_if<std::is_arithmetic<T0>::value && (S > 1 && S < 5)>::type>
struct Vec
{
    using value_type = T0;
    using vector_type = Vec<T0, S>;
    using array_type = std::array<T0, S>;
    using index_type = size_t;
    using size_type = size_t;

    enum { num_components = S };

    array_type v;
};

, such that I can make a vector type with 2, 3 or 4 elements:

    template<typename T0>
    using Vec2 = Vec<T0, 2>;

    template<typename T0>
    using Vec3 = Vec<T0, 3>;

    template<typename T0>
    using Vec4 = Vec<T0, 4>;

Access is of the form v[0], v[1], etc. (for brevity I don't include the [] operator overloads). Sometimes I prefer x, y and so on but don't want the extra "." from naming the structs in the union. So using a non-standard "feature" of Visual Studio 2013, tried to use an anonymous union, only enabling the value if S (dimension) is 2, 3 or 4, as follows:

    template<typename T0, size_t S, typename = typename std::enable_if<std::is_arithmetic<T0>::value && (S > 1 && S < 5)>::type>
    struct Vec
    {
        using value_type = T0;
        using vector_type = Vec<T0, S>;
        using array_type = std::array<T0, S>;
        using index_type = size_t;
        using size_type = size_t;

        enum { num_components = S };

        union
        {
            array_type v;

            template<typename = typename std::enable_if<S == 2>::type>
            struct
            {
                value_type x, y;
            };

            template<typename = typename std::enable_if<S == 3>::type>
            struct
            {
                value_type x, y, z;
            };

            template<typename = typename std::enable_if<S == 4>::type>
            struct
            {
                value_type x, y, z, w;
            };
        };
    };

Unfortunately this gives me the following error:

**error C2332: 'struct' : missing tag name**

And in a way I suppose it is. Is there any way to achieve what I'm trying here? I'm sure enable/disable of an anoymous struct almost certainly gives the compiler a migrane. I can use anonymous union like this if I give the struct a name of course.

timrau
  • 22,578
  • 4
  • 51
  • 64
Robinson
  • 9,666
  • 16
  • 71
  • 115
  • It's value_type, so I can write either myvec.v[0] or myvec.x, for the same element. Of course a 2d vector doesn't have a z and a 3d vector doesn't have a w, so I was looking to get those disabled somehow. – Robinson Jul 22 '16 at 20:10
  • Oh you're right, sorry. Will fix. – Robinson Jul 22 '16 at 20:26

1 Answers1

2

Why do you decide that memory layout of std::array and struct{ T x, T y, ... T} will be identical? It can be reached only if you reset alignment setting to 1 for your class with #pragma pack. For others the alignment is unpredictable.

You want to have such class that

  1. provides access via data member selectors such as .x, .y and so on
  2. provides direct access to data member via operator[]
  3. does not break default data member alignment (std::array is linear, it breaks compiler optimization alignment for your class data member)

The following code meets the above requirements without any non-standard features:

template<typename T, size_t S>
struct Vec;
template<typename T>
struct Vec<T, 1> {
  enum {count = 1};
  T x;
  T& operator[](size_t i) {
    assert(i == 0);
    return x;
  }
  const T& operator[](size_t i) const {
    assert(i == 0);
    return x;
  }
};
template<typename T>
struct Vec<T, 2> {
  enum { count = 2 };
  T x;
  T y;
  T& operator[](size_t i) {
    assert(0 <= i && i < count);
    return this->*(pointer(i));
  }
  const T& operator[](size_t i) const {
    assert(0 <= i && i < count);
    return this->*(pointer(i));
  }
  static T Vec::* pointer(size_t i) {
    static T Vec::* a[count] = { &Vec::x, &Vec::y };
    return a[i];
  }
};
template<typename T>
struct Vec<T, 3> {
  enum { count = 3 };
  T x;
  T y;
  T z;
  T& operator[](size_t i) {
    assert(0 <= i && i < count);
    return this->*(pointer(i));
  }
  const T& operator[](size_t i) const {
    assert(0 <= i && i < count);
    return this->*(pointer(i));
  }
  static T Vec::* pointer(size_t i) {
    static T Vec::* a[count] = { &Vec::x, &Vec::y, &Vec::z };
    return a[i];
  }
};

int main() {
  Vec<int, 2> v1{ 1, 2 };
  assert(v1[0] == v1.x);
  assert(v1[1] == v1.y);

  Vec<unsigned char, 3> v2{ 4, 5, 6 };
  assert(v2[0] == v2.x);
  assert(v2[1] == v2.y);
  assert(v2[2] == v2.z);
  return 0;
}
AnatolyS
  • 4,249
  • 18
  • 28
  • Good point about alignment. For reference there's info here. http://stackoverflow.com/questions/8262963/stdarray-alignment. – Robinson Jul 22 '16 at 21:26
  • @Robinson my point was about that in common case offset of the Nth element of std::array may not be equal to offset of the Nth data member of structure. As the result the idea is to use union can be broken if you do not reset aligment to 1. – AnatolyS Jul 22 '16 at 21:34
  • It is broken, which is why I'm changing my approach to your method (just doing it now). It works OK as I can put operator += outside of the class and just iterate using T::count, thereby having one += function instead of 3. I'm hoping the compiler is smart enough to optimise the loop away in release builds. – Robinson Jul 22 '16 at 21:52
  • @Robinson Good luck! – AnatolyS Jul 22 '16 at 22:01