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:
#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:
#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:
#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:
#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;
}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.
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).
- Los structured bindings (
auto [a, b] = ...) desempaquetan tuples, pairs, structs y arrays de forma limpia std::optionalreemplaza valores centinela;std::variantreemplaza unions inseguras;std::anypara tipos verdaderamente dinamicosstd::string_viewevita copias para acceso de lectura de cadenas — pero cuidado con las referencias colgantesif constexprhabilita 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?