// Un simple ejercicio de C++ hecho quizás más complicado a través del // uso de C++ moderno. Así refresco mi escaso conocimiento de C++. // Creo que esto es la primera vez que usé ranges, `delete` de un // constructor default, el operador nave espacial, `forward_list`, // `find_if`, `pair`, construir objetos con argumentos sin escribirle // un constructor, `mutable`, `noexcept`, `override`, // `std::exception`, `decltype`, overridear por constedad, lambdas, e // inferencia de tipos. También ver hipermercado.cc. #include // https://en.cppreference.com/w/cpp/io/cout #include // https://stackoverflow.com/questions/530614/print-leading-zeros-with-c-output-operator #include #include // https://en.cppreference.com/w/cpp/container/list #include // https://devblogs.microsoft.com/oldnewthing/20230804-00/?p=108547 #include // https://en.cppreference.com/w/cpp/container/forward_list #include #include // GCC 9 no sabe lo suficiente de C++20 para compilar esto, se agregó // en GCC 10.1: #include // para https://en.cppreference.com/w/cpp/algorithm/ranges/find // y https://en.cppreference.com/w/cpp/algorithm/ranges/max_element using std::list, std::ranges::find_if, std::string, std::deque, std::vector, std::forward_list, std::strong_ordering, std::pair, std::ostream, std::cout; class Precio { int centavos; //float pesos; // https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt // Sorprendentemente puedo inlinear esto acá! friend ostream& operator<<(ostream& out, const Precio& p) { // https://stackoverflow.com/questions/530614/print-leading-zeros-with-c-output-operator // https://stackoverflow.com/questions/1714515/how-can-i-pad-an-int-with-leading-zeros-when-using-cout-operator return out << "$" << p.centavos / 100 << "," << std::setfill('0') << std::setw(2) << p.centavos % 100; // return out << "$" << std::fixed << std::setprecision(2) << p.pesos; } public: Precio() = delete; // Precio(float f) : centavos(f * 100 + 0.5) { } // Sin usar https://en.cppreference.com/w/cpp/language/default_comparisons strong_ordering operator<=>(const Precio& o) const { return centavos < o.centavos ? strong_ordering::less : centavos == o.centavos ? strong_ordering::equal : strong_ordering::greater; } }; // Sorprendentemente puedo construir esta clase con sus contenidos sin // escribir ningún constructor. class Proveedor { public: string nombre; Precio precio; }; // Esto también. Es más, puedo construirlo con solo el primer campo. class Producto { public: string descripción; // Vamos a usar std::list en vez de std::forward_list list proveedores; // Alternativamente: //deque proveedores; //forward_list proveedores; const Proveedor& mejor_proveedor() const; void agregar_proveedor(const string& proveedor, Precio precio); }; const Proveedor& Producto::mejor_proveedor() const { // Usando lambdas: https://en.cppreference.com/w/cpp/language/lambda return *std::ranges::min_element(proveedores, [] (const Proveedor& a, const Proveedor& b) {return a.precio < b.precio;}); } void Producto::agregar_proveedor(const string& proveedor, Precio precio) { proveedores.push_front(Proveedor{proveedor, precio}); } // Hereda de https://en.cppreference.com/w/cpp/error/exception class NoSeEncuentra : public std::exception { string cosa; mutable string buf; // Mirar `what` abajo friend ostream& operator<<(ostream& out, const NoSeEncuentra& e) { return out << e.cosa << " [!!@@]"; } public: NoSeEncuentra(const string& cosa_) : cosa(cosa_) {} // Mi teoría sobre `buf` es que necesitamos espacio para alocar la // cadena que dure más que la llamada a `.what()`, pero que se // desaloca en algún momento; entonces la única opción es hacerlo un // campo en este objeto. Resulta que eso es suficiente: // https://en.cppreference.com/w/cpp/error/exception/what // Pero what() es `const`; si se le quitamos `const`, llamadas a // través del vtable de std::exception no la llamarán (y no se // compilará con `override`, y no la podríamos llamarla desde un // bloque `catch` que usa `const`). Entonces `buf` tiene que ser // `mutable`, lo cual semánticamente está bien, creo. const char *what() const noexcept override { if (buf == "") { buf = string("No se encuentra: "); buf += cosa; } return buf.c_str(); } }; class Supermercado { list productos; decltype(productos)::const_iterator buscar_producto(const string& producto) const; decltype(productos)::iterator buscar_producto(const string& producto); public: // https://en.cppreference.com/w/cpp/utility/pair const pair mejor_proveedor(const string& producto) const; void editar_precio(const string& producto, const string& proveedor, Precio precio); void agregar_proveedor(const string& producto, const string& proveedor, Precio precio); }; // Declaramos esto como el tipo de iterador de la colección de // productos porque así podemos cambiar el tipo de la colección con // impunidad. decltype(Supermercado::productos)::iterator Supermercado::buscar_producto(const string& producto) { // Para variar, usamos find_if en vez de un bucle explícito. return find_if(productos, [&] (Producto& pi) { return pi.descripción == producto; }); } // Históricamente tuvimos que duplicar este tipo de cosas para // constness. Creo que ahora hay otra forma de hacerlo pero no lo // entiendo todavía. decltype(Supermercado::productos)::const_iterator Supermercado::buscar_producto(const string& producto) const { return find_if(productos, [&] (const Producto& pi) { return pi.descripción == producto; }); } void Supermercado::editar_precio(const string& producto, const string& proveedor, Precio precio) { auto p = buscar_producto(producto); if (p == productos.end()) throw NoSeEncuentra(producto); auto q = find_if(p->proveedores, [&] (const Proveedor& qi) { return qi.nombre == proveedor; }); if (q == p->proveedores.end()) throw NoSeEncuentra(proveedor); q->precio = precio; } void Supermercado::agregar_proveedor(const string& producto, const string& proveedor, Precio precio) { auto p = buscar_producto(producto); if (p == productos.end()) { // No se encontró productos.push_front(Producto(producto)); p = productos.begin(); } p->agregar_proveedor(proveedor, precio); } const pair Supermercado::mejor_proveedor(const string& producto) const { auto p = buscar_producto(producto); if (p == productos.end()) throw NoSeEncuentra(producto); const Proveedor& q = p->mejor_proveedor(); return pair(q.nombre, q.precio); } int main() { Supermercado m; // Es necesario que los parámetros string sean const para pasarle un // literal, o que no sean referencias (&). No podés pasarle un // temporal como una referencia mutable. // https://stackoverflow.com/questions/58515841/passing-string-literal-to-function-which-expect-string // https://stackoverflow.com/questions/63637762/what-happens-when-a-string-literal-is-passed-to-a-function-accepting-a-const-std m.agregar_proveedor("Bananas", "Dole", 2499.99); m.agregar_proveedor("Bondiola", "San Francisco", 5499.99); m.agregar_proveedor("Bondiola", "Res", 3999.99); try { // ptype en GDB te muestra el tipo de bananas: // https://stackoverflow.com/questions/9568201/gdb-show-typeinfo-of-some-data auto bananas = m.mejor_proveedor("Bananas"); cout << "Mejor bananas son de " << bananas.first << " al precio " << bananas.second << "\n"; //printf("Con C printf tenemos %s\n", bananas.first.c_str()); auto bondiola = m.mejor_proveedor("Bondiola"); cout << "Mejor bondiola es de " << bondiola.first << " al precio " << bondiola.second << "\n"; m.editar_precio("Bondiola", "San Francisco", 3299.99); cout << "San Francisco cortó el precio!\n"; auto bondiola2 = m.mejor_proveedor("Bondiola"); cout << "Mejor bondiola es de " << bondiola2.first << " al precio " << bondiola2.second << " ahora!\n"; } catch (const NoSeEncuentra& e) { std::cerr << e.what() << std::endl; cout << "No se encontró: " << e << "\n"; return 1; } return 0; }