Search code examples
c++structunionstypecast-operator

Problem with overloaded typecast operator for struct within a union


I'm trying to implement a simple vector-swizzling functionality as a pet project to get into template metaprogramming. With the help of open-source mathematics library glm and some other posts on SO, I have come up with a solution which is basically working but has one error.

I have implemenented several structs which hold the data I need to represent a two dimensional eucledian vector. The struct "vec2" has a union which holds a float array with two elements (float data[2]) and two instances of struct "scalarSwizzle" which is supposed to implement the swizzling mechanic which allows me to acces the vector like so vec.data[0] or so vec.x.

Following the code I implemented so far:

    #include <iostream>

    template<typename T>
    void print(T value)
    {
      std::cout << "print func: " << value << std::endl;
    }

    template<typename T, unsigned int I>
    struct scalarSwiz
    {
      T value[1];

      T &operator=(const T newValue)
      {
        value[I] = newValue;
        return value[I];
      }

      operator T()
      {
        return value[I];
      }
    };


    template<typename T>
    struct vec2
    {
      union
      {
        T data[2];
        scalarSwiz<T, 0> x;
        scalarSwiz<T, 1> y;
      };

      vec2()
      {
        x = 0.0f;
        y = 1.0f;
      }

      vec2(T pA, T pB)
      {
        x = pA;
        y = pB;
      }
    };


    int main(int argc, char *args[])
    {
     vec2<float> vec1{5.0f, 1.0f};

     std::cout << "value vec1.data[0]: " << vec1.data[0] << std::endl;
     std::cout << "value vec1.data[1]: " << vec1.data[1] << std::endl;
     std::cout << "value vec1.x: " << vec1.x << std::endl;
     std::cout << "value vec1.y: " << vec1.y << std::endl << std::endl;

     print(vec1.data[0]);
     print(vec1.data[1]);
     print(vec1.x);
     print(vec1.y);

     std::cin.get();

    }

The output is the following:

value vec1.data[0]: 5
value vec1.data[1]: 567.4
value vec1.x: 5
value vec1.y: 567.4

print func: 5
print func: 567.4
print func: 5
print func: 2.5565e-39

I expected the output to be the same for both printing the values directly in main() and via print() but vec.y is not resolved when I print it via the print() function. So I guess something is wrong with the overloaded typecast operator in "scalarSwizzle" but i have no idea what. What I also dont understand is, why visual studio also doesn't resolve the value properly as seen on the following image:

Visual Studio Debugger

vec1.y seem to be pointing to the same physical address then vec.x, while the direct std::cout in main() works fine.

I've been trying for a couple of days now to wrap my head around the problem, why the overloaded typecast operator doesnt work for vec.y but i just dont get it. Maybe someone here can help my with this problem.

Thank you!


Solution

  • First of all

    template<typename T, unsigned int I>
    struct scalarSwiz
    {
      T value[1];
    
      T &operator=(const T newValue)
      {
        value[I] = newValue;
        return value[I];
      }
    
      operator T()
      {
        return value[I];
      }
    };
    

    results in undefined behavior if I != 0 (array access out of bounds) so don't expect your code to be correct or even stable.

    Secondly, accessing an inactive member of a union is also undefined behavior (as per c++ standard). However, msvc, gcc and clang extend the c++ standard so that accessing inactive member behaves like we expect it to.

    And finally, your scalarSwiz type can be replaced by an anonymous struct:

    template<typename T>
    struct vec2
    {
      union
      {
        T data[2];
        struct
        {
           T x, y;
        };
      };
    
      vec2()
      {
        x = 0.0f;
        y = 1.0f;
      }
    
      vec2(T pA, T pB)
      {
        x = pA;
        y = pB;
      }
    };
    

    In regards to your Visual Studio debugger display: this is because of your scalarSwiz definition. You define an array of length 1 T value[1] and you put 2 scalarSwiz objects in a union. Because every member of a union share the same memory (or rather start at the same memory location), both of your value members point to the beginning of the data array. The watch window only displays the members and their values of a certain type, it has no knowledge of your quirky indexing. And because both arrays occupie the same memory, the same value is shown.