views:

119

answers:

5

I'm porting code that uses a very large array of floats, which may trigger malloc failures from c to c++. I asked a question about whether I should use vectors or deques and Niki Yoshiuchi generously offered me this example of a safely wrapped type:

template<typename T>
class VectorDeque
{
private:
  enum TYPE { NONE, DEQUE, VECTOR };
  std::deque<T> m_d;
  std::vector<T> m_v;
  TYPE m_type;
  ...
public:
  void resize(size_t n)
  {
    switch(m_type)
    {
      case NONE:
      try
      {
        m_v.resize(n);
        m_type = VECTOR;
      }
      catch(std::bad_alloc &ba)
      {
        m_d.resize(n);
        m_type = DEQUE;
      }
      break;
    }
  }
};

I needed a 2D vector of vectors/deque of deques, so I modified it to the following code:

template<typename T>
class VectorDeque
{
private:
  enum STORAGE_CONTAINER { NONE, DEQUE, VECTOR };
  std::deque<std::deque<T> > x_d,y_d,z_d;
  std::vector<std::vector<T> > x_v,y_v,z_v;
  TYPE my_container;
public:
  void resize(size_t num_atoms, size_t num_frames)
  {
    switch(m_type)
    {
      case NONE:
      try
      {
        x_v.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   x_v[counter].resize(num_frames);
        y_v.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   y_v[counter].resize(num_frames);
        z_v.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   z_v[counter].resize(num_frames);
        my_container = VECTOR;
      }
      catch(std::bad_alloc &e)
      {
        x_d.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   x_d[counter].resize(num_frames);
        y_d.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   y_d[counter].resize(num_frames);
        z_d.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   z_d[counter].resize(num_frames);
        my_container = DEQUE;
      }
      break;
    }
  }
};

I now want to be able to define my bracket operators so that I can have a statement like x[1][2] directly access whichever is the real memory container I'm using (given by the value of my enumerated variable.

I've seen a couple of tutorials floating around about overriding the brackets operator, but have positively no idea to override double brackets.

How can you overload double brackets?

Additionally, how would you overload double iterators (in case I want to use an iterator, as opposed to direct indexing)?

EDIT 1:

Based on the solution from Martin York/Matteo Italia I devised the following class:

template<typename T>
class VectorDeque2D
{
public:

  class VectorDeque2D_Inner_Set
  {
    VectorDeque2D& parent;
    int   first_index;
  public:
    // Just init the temp object
    VectorDeque2D_Inner_Set(My2D& p, int first_Index) : 
      parent(p), 
      first_Index(first_index) {} 
    // Here we get the value.
    T& operator[](int second_index)  const 
    { return parent.get(first_index,second_index);}   
  };

  // Return an object that defines its own operator[] that will access the data.
  // The temp object is very trivial and just allows access to the data via 
  // operator[]
  VectorDeque2D_Inner_Set operator[](unsigned int first_index) { 
    return (*this, x);
  }


  void resize_first_index(unsigned int first_index) {
    try {
      my_vector.resize(first_index);
      my_container = VECTOR;
    }
    catch(std::bad_alloc &e) {
      my_deque.resize(first_index);
      my_container = DEQUE;
    }
  }

  void resize_second_index(unsigned int second_index) {
    try {
      for (unsigned int couter=0;couter < my_vector.size(); counter++) {
    my_vector[counter].resize(second_index);
      }
      my_container = VECTOR;
    }
    catch(std::bad_alloc &e) {
      for (unsigned int couter=0;couter < my_deque.size(); counter++) {
    my_deque[counter].resize(second_index);
      }
      my_container = DEQUE;
    }
  }
  void resize(unsigned int first_index,
          unsigned int second_index) {
    try {
      my_vector.resize(first_index);
      for (unsigned int couter=0;couter < my_vector.size(); counter++) {
    my_vector[counter].resize(second_index);
      }
      my_container = VECTOR;
    }
    catch(std::bad_alloc &e) {
      my_deque.resize(first_index);
      for (unsigned int couter=0;couter < my_deque.size(); counter++) {
    my_deque[counter].resize(second_index);
      }
      my_container = DEQUE;
    }    
  }
private:
  enum STORAGE_CONTAINER { NONE, DEQUE, VECTOR };

  friend class VectorDeque2D_Inner_Set;

  std::vector<std::vector<T> > my_vector;
  std::deque<std::deque<T> > my_deque;
  STORAGE_CONTAINER my_container;

  T& get(int x,int y) { 
    T temp_val;
    if(my_container == VECTOR) {
      temp_val = my_vector[first_index][second_index];
    }
    else if(my_container == DEQUE) {
      temp_val = my_deque[first_index][second_index];
    }

    return temp_val;
  }

};

Finally a size-safe 2D container!! Thanks guys!

+1  A: 
How can you overload double brackets?

I didn't fully understand your question, but you have to overload brackets, and make them return an object who overloads its own bracket operator.

For example, if you have a vector of vectors, the work is already done: vector < vector < something > > overloads operator[], which returns a vector< something >; this, in turn, has its bracket operator overloaded (and it returns a something object), so you can simply do:

vector<vector<something> > vec;
// ...
something s = vec[2][3];


Example with a proxy object:

template <typename T>
class Container
{
private:
    // ...


public:

    // Proxy object used to provide the second brackets
    template <typename T>
    class OperatorBracketHelper
    {
        Container<T> & parent;
        size_t firstIndex;
    public:
        OperatorBracketHelper(Container<T> & Parent, size_t FirstIndex) : parent(Parent), firstIndex(FirstIndex) {}

        // This is the method called for the "second brackets"
        T & operator[](size_t SecondIndex)
        {
            // Call the parent GetElement method which will actually retrieve the element
            return parent.GetElement(firstIndex, SecondIndex);
        }

    }

    // This is the method called for the "first brackets"
    OperatorBracketHelper<T> operator[](size_t FirstIndex)
    {
        // Return a proxy object that "knows" to which container it has to ask the element
        // and which is the first index (specified in this call)
        return OperatorBracketHelper<T>(*this, FirstIndex);
    }

    T & GetElement(size_t FirstIndex, size_t SecondIndex)
    {
        // Here the actual element retrieval is done
        // ...
    }
}

(add overloaded const methods wherever appropriate :) )

Note that with this method you lose almost nothing in respect to an operator() implementation, since the retrieval is still done in one single place, without constraints on the usage of the two indexes, having both indexes at the moment of performing the retrieval, and without returning "fat" temporary objects (OperatorBracketHelper is just as big as two pointers, and can be easily optimized away by the compiler).

Matteo Italia
I want to be able to reference the data contained inside my type VectorDeque using double brackets. Your example would work differently than what I'm trying to do as I'm not trying to define [] for VectorDeque, I'm trying to define [][] for VectorDeque, because my declaration is simply VectorDeque(); not VectorDeque< VectorDeque <...
Jason R. Mick
Well, it was just an example. Keep in mind that *there's no such thing as a [][] operator*, there's only operator[] which may return a type that overloads again operator[]. In this case, you should create a temporary proxy-object to return in the VectorDeque's operator[], which would keep a reference to its parent object and ask to it the "correct" result when its operator[] is called. I'll try to add an example in a minute.
Matteo Italia
Ah so there is a way, just not via direct overloading...
Jason R. Mick
No; have a look at the example I just added (hoping I got it right :) ).
Matteo Italia
Heck, @Martin York was faster :)
Matteo Italia
@ Matteo Italia: Very good example. Just return the values be reference (so that they are lvalues) thus allowing you to modify them in place. x[3][4] = 5;
Martin York
Nice ... I will test it out briefly. I think you may have the solution! That's all I was looking for .... something where I can access it like a normal 2D vector/deque in the rest of my code, while maintaining that on resize ALL change to the appropriate container class.
Jason R. Mick
@Martin York: fixed, thank you.
Matteo Italia
+1  A: 

Don't overload the [] operator, overload the () operator.

See this link:Overloading Subscript Operator.

I highly suggest reading through the C++ FAQ Lite at least once before posting to Stack Overflow. Also, searching Stack Overflow may yield some useful information also.

Thomas Matthews
+1 for using () instead. It is often overlooked when one implements multi-dimensional indexing. (God knows I've done it more than a few times... :)
Marcus Lindblom
Skimmed over that section and his solution. It mentions you can use [][]. I read his reasoning why you would typically not want to, but I want to preserve the vector-ish/deque-ish character of it, so I think in this case it is worth the downsides. But how would you do it? He doesn't offer much detail. I'm thinking I need nested classes or something, but how do I preserve the try/catch handling in such a nested structure?
Jason R. Mick
@Jason R. Mick: As with many things in the C++FAQ it is very one sided and judgmental and I disagree. Providing the ability to use [][] to make the code readable is the way I would recommend doing it because it makes the code easier to read and thus maintain. (At the cost of making you matrix class slightly harder to read). Below I provide a simple template of how to do it without exposing implementation details and thus provide flexibility (PS. This is a relatively standard pattern (not something I invented)).
Martin York
A: 

There is no "double brackets" operator in C++. What you need to do is define a single [] operator and have it return a reference to another object, which can in turn respond to its own [] operator. This can be nested as many levels deep as you require.

For example, when you create a vector of vectors, the [] operator on the outer vector returns a reference to one of the inner vectors; the [] operator on that vector returns a reference to an individual element of the vector.

std::vector<std::vector<float> > example;
std::vector<float> & first = example[0];  // the first level returns a reference to a vector
float & second = example[0][0];  // the same as first[0]
Mark Ransom
See Matteo's example below for a solution...
Jason R. Mick
@Jason, I don't think you understood my answer. I didn't say you couldn't do it; I started by saying that the most obvious answer was the wrong way to think about the question, then detailed the proper way to get what you want. I thought my explanation was quite simple and straight-forward, please tell me where I failed.
Mark Ransom
Well you seemed hung up on saying that you couldnt overload [][] because it wasn't a real operator. I didn't care about overloading [][], as much as I just wanted to be able to index things using [][] for custom types, without nesting. Martin York and Matteo Italia provided this. Your answers are technically correct to the "T", but you need to think outside the box a little more; in which case your answers would be more helpful.
Jason R. Mick
@Jason, I can see how opening the answer in the way I did might have given you the wrong impression. Certainly I wasn't "hung up" about it, but that critical fact seemed essential to understanding the rest of the answer. I'm sorry you didn't find it helpful, perhaps the next person to find this question will have the opposite opinion.
Mark Ransom
+6  A: 

There are two main techniques:

1) Use operator() rather than operator[].
This is because the operator() allows multiple parameters.

class My2D
{
    public:
       int&   operator()(int x,int y)  { return pget(x,y);}
    private:
       int&   pget(int x,int y) { /* retrieve data from 2D storage */ }
};

2) Use operator[] but return an intermediate object.
You can then apply the second operator[] to the intermediate object.

class My2D
{
    public:
       class My2DRow
       {
           My2D& parent;
           int   x;
           public:
               My2DRow(My2D& p, int theX) : parent(p), x(theX) {}     // Just init the temp object
               int& operator[](int y)  const { return parent.pget(x,y);}   // Here we get the value.
       };

       // Return an object that defines its own operator[] that will access the data.
       // The temp object is very trivial and just allows access to the data via operator[]
       My2DRow operator[](int x)        { return My2DRow(*this, x);}
    private:
       friend class My2DRow;
       int&   pget(int x,int y) { /* retrieve data from 2D storage */ }
};

int main()
{
    My2D   data;
    int&   val = data[1][2]; // works fine.

    // This is the same as
    My2D::My2DRow row  = data[1];
    int&          val2 = row[2]; 
}

I prefer the second technique.
This is because it leaves the original code untouched and more natural to read (in an array context). Of course you pay for the simplicity at the high level with slightly more complex code implementing your 2D array.

Martin York
Nice! Again, what you and Matteo offered is what I would consider a "real" solution. People who were complaining that there was no way to do it were just getting hung up on the fact that there's no singular [][] operator to overload....
Jason R. Mick
A: 

I covered overloading operator[] for a multi-dimensional array in an answer to a previous question.

I'd probably deal with iterators pretty similarly: Have one iterator that represents a "slice" (row or column) of the multi-dimensional array, and then another that represents an element in that slice.

Jerry Coffin
Your answer seems to come from here: http://www.parashift.com/c++-faq-lite/operator-overloading.html#faq-13.10 That still doesn't answer my question, though. Is there a way to use [][]. I will accept an answer that transforms my class to a nested one. However, on a bad malloc, all of my VectorDeque instances need to change to Deque, not just that one....
Jason R. Mick
@Jason: Actually, no. The answer in the FAQ comes from ancient Usenet posts, some of them from me. For example, Robert Martin and I provide the two answers in a thread from 1996, which I believe predates that being included in the FAQ: http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/0a52d34d173dd27d/298b8a42e13f4986
Jerry Coffin