0

The point is to create uniform struct for vertex and for matrix in c++

Pseudocode:

vector<COUNT, T> {
   union {
    T data[COUNT];
    struct { T x, y, z, w; } // if size is 1 -> only x, if 2 -> only x and y ... etc
    struct { T u, v; }    // same   } 
 }

 template<COUNT, VCOUNT, T>
 matrix<COUNT, VCOUNT> : vector<COUNT, vector<VCOUNT, T>> {}

And use like this:

  void foo() {
      v3f pos(0,1,0);
      pos.xy /= pos.z;
      m4f transform = {{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,3.456}}
      transform.x.y = transform[3][2];
   }

I am too close to make this possible, the only thing I can't reach is initialization of matrix:

   v4f v;  // v4f typedef of vector<4,float>, m4f is matrix<4,4,float>
   v = {0,0,1,0}; //work

   m4f mat = {v,v,{},v}; //don't work "excess elements in struct initialization"
   m4f mat = {{v,v,{}, v}}; // same

Some implementation details

namespace math::detail
{
  template<int SIZE, typename T>
  struct vector_base {
    T _[SIZE];
  };

  template<typename T>
  struct vector_base<1, T> {
    union { T _[1]; T x; };
  };

  template<typename T>
  struct vector_base<2, T> {
    union { T _[2]; struct { T x, y; }; struct { T u, v; }; };
  };

  template<typename T>
  struct vector_base<3, T> {
    union { T _[3]; struct { T x, y, z; }; };
  };

  template<typename T>
  struct vector_base<4, T> {
    union { T _[4]; struct { T x, y, z, w; }; };
  };
}

namespace math
{
  enum VecI { X, Y, Z, W, U = X, V = Y };

  template<int SIZE, typename T>
  struct vector : detail::vector_base<SIZE, T>
  {    
      using base = detail::vector_base<SIZE, T>;
      vector() = default;
      template<typename ... Types,
              typename std::enable_if<sizeof...(Types) == SIZE, int>::type = 0>
      constexpr vector(Types ... s) : base{static_cast<T>(s)... } {}

      // some foos, operator overloading
  }

  template<int size, int vector_size, typename type>
  struct matrix : vector<size, vector<vector_size, type>>
  { 
      // special foos only for matrix
  }
}

Vector constructor with parameter pack is not compiling in case of matrix:

error: no matching constructor for initialization of 'vector<3, vector<3, float> >'
      return {{{1,0,0}, {0,1,0}, {0,0,1}}};
              ^~~~~~~~~~~~~~~~~~~~~~~~~~~
/math/vector/base.h:62:15: note: candidate template ignored: requirement 'sizeof...(Types) == 3' was not satisfied [with Types = <>]
    constexpr vector(Types ... s) : base{static_cast<T>(s)... } {}
              ^

Next implementation of matrix at least compile, but need initializing with extra braces {{{},{},{}} and can't inherit some vector properties:

  template<int size, int vector_size, typename type>
  struct matrix
  {
    vector _[size];

    // foos
  }

I am also tried writing constructors for vector_base, for matrix, deduction guides for all of them, nothing works.

How to initialize array of arrays in C++ with automatic deduction (can initialize {5, 0.43, .1f} without errors of type incompatibilities), without implicitly writing types like in m4f mat = {v4f(0,0,1),v4f(0,1,0), v4f(1,0,0)}, and without "excess" nested braces {{{}}}?

My compiler is clang:9.0.0 with c++17, also can use c++2a, or c++20 when it comes.

Jackalope
  • 93
  • 7

1 Answers1

1

Answer to your actual question

C++ is just lousy at deducing things from braced lists. It won't look at the lists, notice your constructor is initializing a member with them, and try to fit the braces to that member. Basically, multi-level brace initialization only works if it doesn't have to deduce anything (e.g. a non-template constructor, aggregate initialization, or a varible-length initializer_list)

To get this working for you, give each base type a simple constructor:

template<typename T>
struct vector_base<2, T> {
    vector_base(T x, T y) : x(x), y(y) { } // <-- add these to each specialization
    union { T _[2]; struct { T x, y; }; struct { T u, v; }; };
};

Then, for the generic derived type, promote the constructor:

template<int SIZE, typename T>
struct vector : detail::vector_base<SIZE, T>
{    
    using detail::vector_base<SIZE, T>::vector_base;
    /* overloads and methods */
};

Now all forms of vector have a non-generic constructor and brace-initialization works as expected.

math::vector<3, math::vector<3, float>> foo {{1,0,0},{0,1,0},{0,0,1}};

Now, about the type punning

There's actually a neat trick for adding array-like access to struct members without breaking strict aliasing here: Overload the [] operator to access a static constexpr array of member-to-pointers to your struct members.

Then there is only one level of indirection between array-like access and member access, in a constant-foldable map. This allows the compiler to, in most cases, produce the same assembly as if you used union punning or a reinterpret_cast to access the member data in a for loop.

Applied to your code:

template <size_t SIZE, typename T>
struct vector_base;

template <typename T>
struct vector_base<1, T> {
    constexpr vector_base(T x) : x(x) {}
    T x;
    constexpr T& operator [](size_t index) { return this->*(vector_base::map[index]); }
    constexpr const T& operator [](size_t index) const { return this->*(vector_base::map[index]); }
    static constexpr T vector_base::* map[1] = {&vector_base::x};
};

template <typename T>
struct vector_base<2, T> {
    constexpr vector_base(T x, T y) : x(x), y(y) {}
    union {
        T x;
        T u;
    };
    union {
        T y;
        T v;
    };
    constexpr T& operator [](size_t index) { return this->*(vector_base::map[index]); }
    constexpr const T& operator [](size_t index) const { return this->*(vector_base::map[index]); }
    static constexpr T vector_base::* map[2] = {&vector_base::x, &vector_base::y};
};

template <typename T>
struct vector_base<3, T> {
    constexpr vector_base(T x, T y, T z) : x(x), y(y), z(z) {}
    T x;
    T y;
    T z;
    constexpr T& operator [](size_t index) { return this->*(vector_base::map[index]); }
    constexpr const T& operator [](size_t index) const { return this->*(vector_base::map[index]); }
    static constexpr T vector_base::* map[3] = {&vector_base::x, &vector_base::y, &vector_base::z};
};

template <typename T>
struct vector_base<4, T> {
    constexpr vector_base(T x, T y, T z, T w) : x(x), y(y), z(z), w(w) {}
    T x;
    T y;
    T z;
    T w;
    constexpr T& operator [](size_t index) { return this->*(vector_base::map[index]); }
    constexpr const T& operator [](size_t index) const { return this->*(vector_base::map[index]); }
    static constexpr T vector_base::* map[4] = {&vector_base::x, &vector_base::y, &vector_base::z, &vector_base::w};
};

Note: the union between x/y and u/v is actually well-formed, even for accessing inactive members

Demo: https://godbolt.org/z/ibpSqB

parktomatomi
  • 3,851
  • 1
  • 14
  • 18