Search code examples
c++templatesunions

Exposing union's variables as class member variables


I've got this code that I just compiled successfully :

template <typename T, unsigned int N>
struct Vector
{
    struct Vec1
    {
        T x;
    };

    struct Vec2 : public Vec1
    {
        T y;
    };

    struct Vec3 : public Vec2
    {
        T z;
    };

    struct Vec4 : public Vec3
    {
        T w;
    };

    template <unsigned int N>
    union Data
    {
        std::array<T, N> components;
    };

    template <>
    union Data<1>
    {
        Vec1 vec;
        std::array<T, 1> components;
    };

    template <>
    union Data<2>
    {
        Vec2 vec;
        std::array<T, 2> components;
    };

    template <>
    union Data<3>
    {
        Vec3 vec;
        std::array<T, 3> components;
    };

    template <>
    union Data<4>
    {
        Vec4 vec;
        std::array<T, 4> components;
    };

    Data<N> data;
};

It works as intended, however I would like the struct Vector to expose the data's variables as its own member variables.

Is it possible?

The solution would allow me to do Vector<int, 3> vec; vec.x ...; vec.components[0] ...;

The purpose of the union is to access easily both the vector's components as an array and individually.

Also, if you happen to know a better way to implement the templated union Data specializations, please say so as I find it kinda hard coded. It would be perfect to recursively add variables without having to add the variables of the previous specialization.

For example, I would only need to declare T x once.


Solution

  • I think you need to bring some clarity to your design and the code.

    Use of

    template <>
    union Data<3>
    {
        T x;
        T y;
        T z;
        std::array<T, 3> components;
    };
    

    does not sound right. You need to have {x, y, z} or components, not x, or y, or z, or components. What you need is something along the lines of

    template <>
    union Data<3>
    {
        struct
        {
           T x;
           T y;
           T z;
        } members;
        std::array<T, 3> components;
    };
    

    Having said that, the cleanest member variable is just

        std::array<T, N> components;
    

    As far as the member variables are concerned, Vector can be defined as:

    template <typename T, unsigned int N>
    struct Vector
    {
       std::array<T, N> components;
    };
    

    If you need to expose the elements of components through x, y, and z-like abstractions, it will be better to add member functions.

    template <typename T, unsigned int N>
    struct Vector
    {
       std::array<T, N> components;
    
       T& x()
       {
          static_assert(N > 0);
          return components[0];
       }
    
       T& y()
       {
          static_assert(N > 1);
          return components[1];
       }
    
       T& z()
       {
          static_assert(N > 2);
          return components[2];
       }
    };
    

    with the above definition of Vector, the following main function should work.

    int main()
    {
       Vector<int, 1> v1;
       v1.x() = 20;
    
       Vector<int, 2> v2;
       v2.x() = 20;
       v2.y() = 30;
    
       Vector<int, 3> v3;
       v3.x() = 20;
       v3.y() = 30;
       v3.z() = 40;
    }
    

    If you use

       Vector<int, 2> v2;
       v2.z() = 20;
    

    you should get a compile-time error.

    You can add the const versions of the above functions to make the member functions work with const objects too.

    template <typename T, unsigned int N>
    struct Vector
    {
       std::array<T, N> components;
    
       T& x()
       {
          static_assert(N > 0);
          return components[0];
       }
    
       T const& x() const
       {
          static_assert(N > 0);
          return components[0];
       }
    
       T& y()
       {
          static_assert(N > 1);
          return components[1];
       }
    
       T const& y() const
       {
          static_assert(N > 1);
          return components[1];
       }
    
       T& z()
       {
          static_assert(N > 2);
          return components[2];
       }
    
       T const& z() const
       {
          static_assert(N > 2);
          return components[2];
       }
    };