Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++ Alias for an array element

I have a (column) Vector class which contains an array of values that can be accessed like:

Vec<int, 4> v();
v[0] = -2; // <- set first value to -2
v[1] = 1; // <- set second value to 1
....

But here is my question: How do i create an alias for v[0], v[1], v[2], v[3]?. I would like to define the first 4 values as v.x, v.y, v.z, v.w:

Vec<int, 4> v();
v.x = -2; // <- set first value to -2
v.y = 1; // <- set second value to 1
v.z = 4; // <- set third value to 4
v.w = 2; // <- set fourth value to 2

I should be able to assign and read the values, and I don't want them to be looking like a function, so accessing the first value like:

Vec<int, 4> v();
v.x() = -2; // <- set first value to -2

Is no good. On top of that the vector class is templated and x should only be defined for dimensions >= 1, and y only for dimenions >= 2 ... and so on... How do I achieve this?

Edit: The Vector class has nothing to do with std::vector, it is a mathematical vector resembling an array, in that it is of fixed size and its only used for mathematical operations. (renaming Vector to Vec).

What I have tried:

Matrix class:

template <typename T, size_t ROWS, size_t COLS>
class Matrix {

    public:
        T& operator[] (size_t idx) {return m_matrix[idx];}
        T operator[] (size_t idx) const {return m_matrix[idx];}

    private:
        m_matrix[ROWS * COLS]
};

Vector class:

template <typename T, size_t N>
class Vec: public Matrix<T, 1, N>{

    public:

        T& x() {return (*this)[0];}
        T x() const {return (*this)[0];}

        T& y() {return (*this)[1];}
        T y() const {return (*this)[1];}

        T& z() {return (*this)[2];}
        T z() const {return (*this)[2];}

        T& w() {return (*this)[3];}
        T w() const {return (*this)[3];}
};

This works and I am easily able to use enable_if to remove the functions if it is not defined for this dimension, this however isn't syntactically pleasing. Ive tried using references:

template <typename T, size_t N>
class Vec: public Matrix<T, N, 1>{

    public:

        T& x = (*this)[0];
        T& y = (*this)[1];
        T& z = (*this)[2];
        T& w = (*this)[3];
};

But this doesn't work, it doesn't give me an error, but it also does not set the values correctly, when I access them after setting they are undefined.

Edit nr 2: there might just exist an even simpler solution, when my last attempt with references is compiled using Visual Studio community 2015's default compiler, then it works. But when I compile it in Code::Blocks using the GNU GCC compiler, then it doesn't. What does the standard say? Is my solution using references allowed, which compiler is wrong?

like image 776
Jens Modvig Avatar asked Jan 03 '18 15:01

Jens Modvig


2 Answers

This:

template <typename T, int D> struct Vec;

// You have to manually specialize for all needed sizes
template <typename T> struct Vec<T, 4>
{
    T x, y, z, w;

    T &operator[](int index)
    {
        switch (index)
        {
            default: // throw or something?
            case 0: return x;
            case 1: return y;
            case 2: return z;
            case 3: return w;
        }
    }
    const T &operator[](int index) const
    {
        switch (index)
        {
            default: // throw or something?
            case 0: return x;
            case 1: return y;
            case 2: return z;
            case 3: return w;
        }
    }
};

The switching on index is not optimal, but at least it's well-defined.

For matrices I prefer to use Vec<Vec<T, Height>, Width>, which makes mat[x][y] notation work. (Swap x and y if you want to.)

like image 174
HolyBlackCat Avatar answered Oct 26 '22 14:10

HolyBlackCat


If you accept a C++14 solution, I propose the creation of a template indexed wrapper for x, y, z and w, referenced to T variables

template <typename T, std::size_t>
struct wrapper
 { wrapper (T const &) {} };

template <typename T>
struct wrapper<T, 0U>
 { T & x; };

template <typename T>
struct wrapper<T, 1U>
 { T & y; };

template <typename T>
struct wrapper<T, 2U>
 { T & z; };

template <typename T>
struct wrapper<T, 3U>
 { T & w; };

Next an std::array wrapper that has to be inherited before the indexed wrappers

template <typename T, std::size_t N>
struct arrayWrp
 { std::array<T, N> arr {}; };

Now you can define an helper struct VecH as follows

template <typename T, std::size_t ... Is>
struct VecH<T, std::index_sequence<Is...>>
   : public arrayWrp<T, sizeof...(Is)>, public wrapper<T, Is>...
 {
   using arrayWrp<T, sizeof...(Is)>::arr;

   VecH () : arrayWrp<T, sizeof...(Is)>{}, wrapper<T, Is>{ arr[Is] }...
    { }

   T & operator[] (std::size_t i)
    { return arr[i]; }

   T const & operator[] (std::size_t i) const
    { return arr[i]; }
 };

that inherit from arrayWrp and from all wrapper<T, Is> needed and that link references x, y, z and w to arr[0], arr[1], arr[2] and arr[3] rispectively

So Vec become

template <typename T, std::size_t N>
struct Vec : public VecH<T, std::make_index_sequence<N>>
 { };

The following is a full working example

#include <array>
#include <iostream>
#include <type_traits>

template <typename T, std::size_t>
struct wrapper
 { wrapper (T const &) {} };

template <typename T>
struct wrapper<T, 0U>
 { T & x; };

template <typename T>
struct wrapper<T, 1U>
 { T & y; };

template <typename T>
struct wrapper<T, 2U>
 { T & z; };

template <typename T>
struct wrapper<T, 3U>
 { T & w; };

template <typename T, std::size_t N>
struct arrayWrp
 { std::array<T, N> arr {}; };

template <typename, typename>
struct VecH;

template <typename T, std::size_t ... Is>
struct VecH<T, std::index_sequence<Is...>>
   : public arrayWrp<T, sizeof...(Is)>, public wrapper<T, Is>...
 {
   using arrayWrp<T, sizeof...(Is)>::arr;

   VecH () : arrayWrp<T, sizeof...(Is)>{}, wrapper<T, Is>{ arr[Is] }...
    { }

   T & operator[] (std::size_t i)
    { return arr[i]; }

   T const & operator[] (std::size_t i) const
    { return arr[i]; }
 };

template <typename T, std::size_t N>
struct Vec : public VecH<T, std::make_index_sequence<N>>
 { };

int main ()
 { 
   Vec<int, 4U>  v4;

   v4.x = 1;
   v4.y = 2;
   v4.z = 3;
   v4.w = 4;

   std::cout << "v4: ";

   for ( auto ui = 0U ; ui < 4U ; ++ui )
      std::cout << ' ' << v4[ui];

   std::cout << std::endl;

   Vec<int, 5U>  v5;  // also over 4

   Vec<int, 3U>  v3;

   v3.x = 10;
   v3.y = 20;
   v3.z = 30;
   // v3.w = 40;  // compilation error

 }

If you don't like the use of the VecH helper struct, you can use partial specialization and a template parameter defaulted to std::make_index_sequence<N> as follows

template <typename, std::size_t N, typename = std::make_index_sequence<N>>
struct Vec;

template <typename T, std::size_t N, std::size_t ... Is>
struct Vec<T, N, std::index_sequence<Is...>>
   : public arrayWrp<T, N>, public wrapper<T, Is>...
 {
   using arrayWrp<T, sizeof...(Is)>::arr;

   Vec () : arrayWrp<T, sizeof...(Is)>{}, wrapper<T, Is>{ arr[Is] }...
    { }

   T & operator[] (std::size_t i)
    { return arr[i]; }

   T const & operator[] (std::size_t i) const
    { return arr[i]; }
 };

but I don't know if it's a good idea: someone could try to use Vec as follows

Vec<int, 3U, std::index_sequence<0, 2, 5>>  v;
like image 25
max66 Avatar answered Oct 26 '22 13:10

max66