#include <iostream>

template<class Type>
class Array2D{
      
private:
      int lines, columns;
      Type **a;
      
      void deallocate() {
         if (lines){    
             for (int i = 0; i < lines; i++){
                 delete[] a[i];
             }
             delete[] a;    
             a = NULL;
         }
         lines = 0;
         columns = 0;
      }

      void clone(const Array2D<Type>& array2D) {
             lines = array2D.lines;
             columns = array2D.columns;
             a = new Type*[lines];
             for (int i = 0; i < lines; i++){
                    a[i] = new Type[columns];
                    for (int j = 0; j < columns; j++){
                        a[i][j] = array2D.a[i][j];
                    }
             }
      }
      
public:
       class iterator{
            
             Array2D<Type>& a;
             int index;

             iterator(int index, Array2D<Type>& a) : index(index), a(a) { }  
       public:        
             inline Type& operator* () {  
                    return a[index/a.columns][index%a.columns];
             }
            
             inline iterator& operator++ () {
                    index++;
                    return (*this);
             }
            
             inline bool operator == (const iterator& right) const {
                    return index == right.index;
             }
            
             inline bool operator != (const iterator& right) const {
                    return index != right.index;
             }
            
             friend class Array2D<Type>;
       };
      
       inline iterator begin(){
              return iterator(0, *this);
       }
      
       inline iterator end(){
              return iterator(lines*columns, *this);
       }
      
       Array2D<Type>() : lines(0), columns(0), a(NULL) { }
      
       Array2D<Type>(int lines, int columns) : lines(lines), columns(columns) {
           a = new Type*[lines];
           for (int i = 0; i < lines; ++i){
               a[i] = new Type[columns];
           }
       }
      
       Array2D<Type>(const Array2D<Type>& rightValue) {
           clone(rightValue);
       }
      
       virtual ~Array2D<Type>(){
           deallocate();
       }
      
       inline Type* operator[] (int line){
             return a[line];
       }
      
       Array2D<Type>& operator= (const Array2D<Type>& rightValue){
             deallocate();
             clone(rightValue);
             return (*this);
       }
      
       template<class T> friend std::ostream& operator<< (std::ostream&, Array2D<T>&);
};

template<class Type>
std::ostream& operator<< (std::ostream& out, Array2D<Type>& array2D){
       out << "{";
       for (int i = 0; i < array2D.lines; ++i){
           out << (i ? "," : "") << "{";
           for (int j = 0; j < array2D.columns; ++j){
               out << (j ? "," : "") << array2D.a[i][j];
           }
           out << "}";
       }
       return out << "}";
}

int main()
{
    // Creez o matrice de intr-uri 2x2
    Array2D<int> a(2,2);    
    
    // Creez si atribui o matrice de int-uri 2x2 (de fapt, aici se cloneaza prin apelul copy-constructorului)
    Array2D<int> b = a;    
    
    // Parcurg o matrice de int-uri folosind un iterator. Observatie: nu trebuie sa stiu dinainte de dimensiuni are!
    for (Array2D<int>::iterator it = a.begin(); it != a.end(); ++it){
        (*it) = 6;
    }
    
    // Afisez o matrice de int-uri, fara sa trebuiasca sa stiu dinainte ce format intern are!
    std::cout << a << " == " << (b = a) << " ?\n";
    
    return 0;
}