DMarlon Studio

Lección 15 · 15 de 20

optional, variant y visit · C++

Represente en C++17 valores que pueden faltar con std::optional y valores de varios tipos con std::variant y std::visit: puertos y celdas.

C++ · optional, variant y visit en DMarlon Studio: el código del ejemplo y su salida en la terminal

`std::optional<int>` es un int que puede no estar. findPort devuelve el puerto si el servicio aparece en la configuración y `std::nullopt` si no, sin recurrir a valores especiales como -1. El propio tipo de retorno avisa de que hay que comprobar el resultado.

Un optional vale true en una condición si tiene valor, y *port lo lee. `value_or(25)` da el valor o, si falta, el que se le pasa: el correo no está configurado y se usa el puerto 25.

`std::variant<int, double, std::string>` guarda exactamente uno de esos tipos cada vez, como una celda de una hoja de cálculo, que puede tener un entero, un decimal o un texto. using Cell = ... le da un nombre corto, y la fila row mezcla los tres tipos en un solo vector.

`std::visit` llama a la función adecuada según el tipo que guarde cada celda. CellPrinter es un visitante: un struct con un operator() para cada tipo. Si faltara alguno y no hubiera una conversión posible, el programa no compilaría, lo que evita olvidar casos.

Para preguntar por un tipo concreto está `std::holds_alternative<double>`, y `std::get<std::string>` saca el valor; si el tipo no coincide, lanza la excepción std::bad_variant_access.

Código · main.cpp

#include <iostream>#include <map>#include <optional>#include <string>#include <variant>#include <vector>// optional: a value that may be missing, with no magic numbers such as -1.std::optional<int> findPort(const std::map<std::string, int>& config, const std::string& service) {    auto it = config.find(service);    if (it == config.end()) return std::nullopt;    return it->second;}using Cell = std::variant<int, double, std::string>;   // a spreadsheet cell: one of thesestruct CellPrinter {                 // a visitor: one operator() per type    void operator()(int n) const { std::cout << "int    " << n << "\n"; }    void operator()(double d) const { std::cout << "double " << d << "\n"; }    void operator()(const std::string& s) const { std::cout << "text   \"" << s << "\"\n"; }};int main() {    std::map<std::string, int> config = {{"http", 8080}, {"db", 5432}};    for (std::string service : {"db", "mail"}) {        std::optional<int> port = findPort(config, service);        if (port) std::cout << service << " port: " << *port << "\n";        else std::cout << service << " port: not set, default " << port.value_or(25) << "\n";    }    std::vector<Cell> row = {42, 3.75, std::string("Total"), 7};    for (const Cell& c : row) std::visit(CellPrinter{}, c);    std::cout << "Cell 1 is a double? " << (std::holds_alternative<double>(row[1]) ? "yes" : "no")              << ", cell 2 says " << std::get<std::string>(row[2]) << "\n";}

Salida · c++ -std=c++17 main.cpp -o main && ./main

db port: 5432
mail port: not set, default 25
int    42
double 3.75
text   "Total"
int    7
Cell 1 is a double? yes, cell 2 says Total

Para recordar

  • std::optional<T>: un T o nada (std::nullopt).
  • Compruebe el optional con if antes de leerlo con *, o use value_or.
  • std::variant<A, B, C> guarda uno de esos tipos cada vez.
  • std::visit con un visitante atiende todos los tipos del variant.
  • std::get<T> lanza una excepción si el variant no guarda un T.

Ejercicio

Añada bool a los tipos de Cell y una celda true a la fila. Verá que sale como int 1, porque el bool se convierte en int para llamar al visitante. Añada a CellPrinter un operator()(bool) que escriba bool yes.