#include <algorithm>
#include <iostream>
#include <stdexcept>          // para std::out_of_range

template<typename T>
class arreglo_dinamico {
   int cap, tam;
   T* p;

public:
   arreglo_dinamico( ) {
      cap = 1;
      tam = 0;
      p = new T[1];
   }

   explicit arreglo_dinamico(int t, T v) {
      cap = t;
      tam = t;
      p = new T[cap];
      for (int i = 0; i < tam; ++i) {
         p[i] = v;
      }
   }

   arreglo_dinamico(std::initializer_list<T> lista) {
      cap = 1;
      tam = 0;
      p = new T[1];
      for (T v : lista) {
         agrega(v);
      }
   }

   arreglo_dinamico(const arreglo_dinamico& v) {
      cap = v.tam;
      tam = v.tam;
      p = new T[cap];
      for (int i = 0; i < tam; ++i) {
         p[i] = v.p[i];
      }
   }

   ~arreglo_dinamico( ) {
      delete[] p;
   }

   void operator=(const arreglo_dinamico& v) {
      if (p == v.p) {
         return;
      }

      delete[] p;
      cap = v.tam;
      tam = v.tam;
      p = new T[cap];
      for (int i = 0; i < tam; ++i) {
         p[i] = v.p[i];
      }
   }

   int size( ) const {
      return tam;
   }

   T& operator[](int i) {
      return p[i];
   }

   const T& operator[](int i) const {
      return p[i];
   }

   T& at(int i) {                // similar al operator[] pero validando el índice; también existe en std::vector
      valida_indice(i);
      return p[i];
   }

   const T& at(int i) const {    // similar al operator[] pero validando el índice; también existe en std::vector
      valida_indice(i);
      return p[i];
   }

   void agrega(T v) {
      if (tam == cap) {
         T* q = new T[2 * cap];
         for (int i = 0; i < tam; ++i) {
            q[i] = p[i];
         }
         delete[] p;
         p = q;
         cap *= 2;
      }

      p[tam] = v;
      tam += 1;
   }

   void quita_ultimo( ) {
      tam -= 1;
   }

private:
   void valida_indice(int i) const {
      if (i < 0 || i >= tam) {
         throw std::out_of_range("Indice invalido");     // un struct estándar para excepciones; se construye una cadena con el mensaje de error
      }
   }
};

int main( ) {
   arreglo_dinamico<int> a = { 10, 5 };
// std::cout << a[4] << "\n";             // esta línea está terriblemente mal ya que el índice es inválido; el programa puede aparentar funcionar o puede morir muy feo
   try {
      std::cout << a.at(4) << "\n";       // esta línea igualmente está mal pero al menos la función miembro .at valida el índice y se eleva una excepción si es inválido
   } catch (const std::exception& ex) {   // std::exception es el tipo general de las excepciones contempladas en la biblioteca de C++
      std::cout << ex.what( ) << "\n";    // la función miembro .what( ) devuelve el mensaje de error
   }
}
