Ir al contenido

C++17: El Estandar Practico

Explora las caracteristicas mas impactantes de C++17: structured bindings, std::optional/variant/any, std::string_view, std::filesystem, if constexpr y nuevos atributos.

Por Que Importa C++17

C++17 es frecuentemente llamado el "estandar practico" porque introdujo una gran cantidad de caracteristicas que simplifican la codificacion diaria sin requerir conocimiento profundo de metaprogramacion con plantillas. Elimino codigo heredado (como std::auto_ptr y trigrafos), agrego tipos de vocabulario (std::optional, std::variant, std::any), e introdujo azucar sintactico que hace el codigo mas corto y mas claro. Si estas escribiendo C++ en produccion hoy, C++17 deberia ser tu linea base minima.

Structured Bindings y Sentencias Inicializadoras

Los structured bindings te permiten desempaquetar agregados (structs, arrays, pairs, tuples) en variables con nombre en una sola declaracion. Combinados con inicializadores de if/switch, eliminan muchas variables temporales:

structured_bindings.cpp
#include <iostream>
#include <map>
#include <string>
#include <tuple>

std::tuple<std::string, int, double> get_employee() {
    return {"Alice", 42, 95000.0};
}

int main() {
    // Structured binding with tuple
    auto [name, age, salary] = get_employee();
    std::cout << name << " is " << age << " years old\n";

    // Structured binding with map iteration
    std::map<std::string, int> scores{{"Alice", 95}, {"Bob", 87}};
    for (const auto& [student, score] : scores) {
        std::cout << student << ": " << score << "\n";
    }

    // if with initializer — the variable is scoped to the if/else
    if (auto it = scores.find("Alice"); it != scores.end()) {
        std::cout << "Found: " << it->second << "\n";
    } else {
        std::cout << "Not found\n";
    }
    // 'it' no longer exists here — no scope leakage

    // Structured binding with array
    int arr[3] = {10, 20, 30};
    auto [x, y, z] = arr;
    std::cout << x << ", " << y << ", " << z << "\n";

    return 0;
}

Tipos de Vocabulario: optional, variant, any

std::optional representa un valor que puede o no estar presente (reemplazando valores centinela y parametros de salida). std::variant es una union con seguridad de tipos. std::any contiene cualquier tipo copiable con verificacion de tipo en tiempo de ejecucion:

vocabulary_types.cpp
#include <iostream>
#include <optional>
#include <variant>
#include <any>
#include <string>

// optional: replaces "return -1 on failure" patterns
std::optional<int> find_index(const std::string& haystack, char needle) {
    for (size_t i = 0; i < haystack.size(); ++i) {
        if (haystack[i] == needle) return static_cast<int>(i);
    }
    return std::nullopt;  // no value
}

int main() {
    // std::optional
    auto idx = find_index("hello", 'l');
    if (idx.has_value()) {
        std::cout << "Found at index " << *idx << "\n";       // 2
    }
    std::cout << idx.value_or(-1) << "\n";  // safe default

    // std::variant — type-safe union
    std::variant<int, double, std::string> value = "hello";
    std::cout << std::get<std::string>(value) << "\n";

    // Visit pattern — exhaustive handling of all types
    std::visit([](const auto& v) {
        std::cout << "Value: " << v << "\n";
    }, value);

    value = 3.14;  // now holds a double
    std::cout << std::get<double>(value) << "\n";

    // std::any — truly any type, with runtime checking
    std::any data = 42;
    std::cout << std::any_cast<int>(data) << "\n";
    data = std::string("world");
    std::cout << std::any_cast<std::string>(data) << "\n";

    return 0;
}

string_view y filesystem

std::string_view es una referencia ligera y no propietaria a una cadena. Evita copias cuando solo necesitas leer una cadena. std::filesystem proporciona manipulacion de rutas y operaciones de archivos portables y multiplataforma:

string_view_fs.cpp
#include <iostream>
#include <string>
#include <string_view>
#include <filesystem>

namespace fs = std::filesystem;

// string_view avoids copying — just a pointer + length
void print_trimmed(std::string_view sv) {
    auto start = sv.find_first_not_of(' ');
    auto end = sv.find_last_not_of(' ');
    if (start != std::string_view::npos) {
        std::cout << sv.substr(start, end - start + 1) << "\n";
    }
}

int main() {
    std::string name = "  Hello, C++17!  ";
    print_trimmed(name);       // no copy — string_view binds to string
    print_trimmed("  world  "); // no copy — binds to string literal

    // std::filesystem — portable path operations
    fs::path source_dir = "/home/user/project/src";
    std::cout << "Filename: " << source_dir.filename() << "\n";
    std::cout << "Parent:   " << source_dir.parent_path() << "\n";
    std::cout << "Exists:   " << fs::exists(source_dir) << "\n";

    // Iterate directory entries
    for (const auto& entry : fs::directory_iterator(".")) {
        if (entry.is_regular_file()) {
            std::cout << entry.path().filename()
                      << " (" << entry.file_size() << " bytes)\n";
        }
    }

    // Create directories recursively
    fs::create_directories("output/logs/2024");

    return 0;
}

if constexpr, CTAD y Mas

if constexpr habilita la ramificacion en tiempo de compilacion dentro de plantillas — las ramas que no coinciden se descartan completamente. Class Template Argument Deduction (CTAD) te permite omitir argumentos de plantilla cuando el compilador puede deducirlos. Las fold expressions simplifican los paquetes de parametros de plantillas variadicas:

if_constexpr.cpp
#include <iostream>
#include <string>
#include <vector>
#include <type_traits>

// if constexpr — branches resolved at compile time
template<typename T>
std::string to_string_safe(T value) {
    if constexpr (std::is_arithmetic_v<T>) {
        return std::to_string(value);
    } else if constexpr (std::is_same_v<T, std::string>) {
        return value;
    } else {
        return "[unsupported type]";
    }
}

// Fold expressions — collapse parameter packs
template<typename... Args>
auto sum(Args... args) {
    return (args + ...);  // unary right fold
}

template<typename... Args>
void print_all(Args&&... args) {
    ((std::cout << args << " "), ...);  // fold over comma
    std::cout << "\n";
}

int main() {
    std::cout << to_string_safe(42) << "\n";
    std::cout << to_string_safe(std::string("hi")) << "\n";

    std::cout << sum(1, 2, 3, 4, 5) << "\n";  // 15
    print_all(1, "hello", 3.14);               // 1 hello 3.14

    // CTAD — no need for std::vector<int>
    std::vector v{1, 2, 3, 4, 5};   // deduced as vector<int>
    std::pair p{"hello", 42};        // deduced as pair<const char*, int>

    // Nested namespaces
    // namespace A::B::C { } instead of namespace A { namespace B { namespace C { } } }

    // Inline variables — can be defined in headers without ODR violations
    // inline constexpr int version = 17;

    return 0;
}
Buena práctica

C++17 estandarizo tres atributos importantes:

[[nodiscard]] — Advierte si un valor de retorno es descartado. Usalo en funciones cuyo valor de retorno siempre debe ser verificado (codigos de error, recursos asignados, funciones fabrica).

[[maybe_unused]] — Suprime advertencias de variables/parametros no utilizados. Util para variables usadas solo en compilaciones de depuracion o codigo especifico de plataforma.

[[fallthrough]] — Indica fallthrough intencional en un caso de switch, silenciando advertencias del compilador.

Adopta [[nodiscard]] agresivamente en tus APIs. Un codigo de error descartado es un bug esperando a ocurrir.

Error común

Nunca retornes un std::string_view que referencia una cadena local. Como string_view no posee sus datos, la cadena subyacente puede ser destruida antes de que la vista sea usada, causando comportamiento indefinido:

// PELIGROSO — retorna una vista a un temporal destruido!
std::string_view bad() {
    std::string s = "hello";
    return s;  // s es destruido al final de la funcion!
}

Regla general: usa string_view para parametros (lectura de datos), retorna std::string para valores de retorno (datos con propiedad).

Puntos clave
  • Los structured bindings (auto [a, b] = ...) desempaquetan tuples, pairs, structs y arrays de forma limpia
  • std::optional reemplaza valores centinela; std::variant reemplaza unions inseguras; std::any para tipos verdaderamente dinamicos
  • std::string_view evita copias para acceso de lectura de cadenas — pero cuidado con las referencias colgantes
  • if constexpr habilita ramificacion en tiempo de compilacion en plantillas, eliminando el boilerplate de SFINAE
  • CTAD permite al compilador deducir argumentos de plantilla: std::vector v{1,2,3} simplemente funciona
  • Usa [[nodiscard]] en funciones cuyos valores de retorno no deben ser ignorados

Quiz — Pon a prueba tus conocimientos

(15 XP)

1. Que hace `auto [x, y] = std::make_pair(1, 2.0);` en C++17?

2. Que representa `std::optional<int>`?

3. Por que es peligroso retornar un `std::string_view` desde una funcion si referencia una cadena local?