Modern C++ (11 → 23): highlights que vale la pena adoptar
Modern C++ no es una feature. Es una presión para hacer explícito el ownership, pasar views en vez de pares puntero-longitud, acotar templates antes de que exploten y mover bookkeeping aburrido a tipos. El subset útil es el que vuelve el código más local, más chequeable y más fácil de borrar para el optimizer. El subset peligroso es el que vuelve invisibles runtime allocation, dispatch, lifetime o complejidad de build.
El reset: adoptá Modern C++ como vocabulario para invariantes, no como decoración. Una feature vale la pena cuando hace más fácil razonar sobre el código generado, el borde de ownership o el camino de error.
Cómo funciona de verdad
C++11 fue el gran reset: move semantics, lambdas, auto, nullptr, range-for,
constexpr, enum class, smart pointers, threads y la primera ola de vocabulary types.
C++14 y C++17 hicieron vivible ese reset con generic lambdas, mejor constexpr,
std::optional, std::variant, std::string_view, structured bindings, if constexpr,
filesystem y hooks de parallel algorithms. C++20 agregó concepts, ranges, std::span,
std::jthread, coroutines, std::source_location, spaceship comparison, modules y mejores
herramientas de compile time. C++23 llenó huecos importantes de biblioteca como
std::expected, más ranges, std::to_underlying y mejoras de constexpr y vocabulary types.
La historia de implementación es familiar desde el resto de este atlas:
| Familia de features | Qué te da | Realidad low-level |
|---|---|---|
| Move semantics | transferir ownership o buffers sin copiar | los objetos moved-from todavía existen y deben ser válidos |
| RAII y smart pointers | cleanup automático en cada salida de scope | las llamadas a destructores son código real en bordes de lifetime |
| Lambdas y templates | callable types visibles para inlining | visibilidad de headers y tamaño de código importan |
std::optional y std::variant |
estados explícitos maybe/one-of | el object layout crece para contener tags y alternativas |
std::span y std::string_view |
views puntero-más-tamaño | sin ownership y normalmente sin bounds checks |
| Concepts | requirements de template legibles | chequeo en compile time, no guard de runtime |
| Ranges | pipelines de iteración componibles | laziness e iterator categories afectan los loops generados |
std::expected |
return type value-or-error | la disponibilidad C++23 depende del toolchain |
Usá feature-test macros y tablas de compiler support cuando cruces el borde C++20/C++23. Que una feature esté estandarizada no significa que tu compiler, standard library, setup de sanitizers o target embedded/freestanding la tenga.
Artefacto ejecutable: un corte de vocabulario C++20
El demo ejecutable vive en examples/modern-cpp/modern-cpp-11-to-23-highlights/. Usa un
subset C++20 que mapea bien a systems code: std::span, std::optional, std::variant,
concepts, structured bindings, lambdas y constexpr.
cd examples/modern-cpp/modern-cpp-11-to-23-highlights
./run.sh
La función constrained dice qué clase de objeto acepta antes de que template instantiation se convierta en una pared de diagnostics:
template <typename T>
concept HasCycleCount = requires(T const& item) {
{ item.name } -> std::convertible_to<std::string_view>;
{ item.cycles } -> std::convertible_to<int>;
};
template <HasCycleCount T>
int total_cycles(std::span<T const> samples)
{
return std::accumulate(samples.begin(), samples.end(), 0, [](int total, T const& sample) {
return total + sample.cycles;
});
}
El script también emite demo.O2.s. En ese assembly, los concepts no aparecen como checks
de runtime; constrain el template en compile time. std::span se pasa como metadata de
view, el cuerpo de la lambda es visible para el optimizer y los hechos restantes de runtime
vienen del control flow real: el check de optional budget y la selección de variant.
Qué adoptar primero
nullptr,enum class,autocuando el tipo es obvio y range-for eliminan categorías enteras de trampas accidentales de compatibilidad C.- RAII,
std::unique_ptr,std::make_uniquey scoped locks vuelven el cleanup una propiedad del lifetime en vez de una convención en cada return path. - Move-only types dejan que las APIs expresen transferencia de ownership sin comentarios.
std::span<T>ystd::string_viewreemplazan parámetros puntero-más-longitud cuando el callee no posee los datos.std::optional<T>sirve para "quizás un valor"; no alcanza cuando necesitás saber por qué falta el valor.std::variantsirve para un conjunto cerrado de alternativas; normalmente conviene visitarlo cerca de donde las alternativas significan algo.- Concepts vale la pena adoptarlos para APIs genéricas públicas e internals template-heavy porque mueven fallas a constraints con nombre.
std::expected<T, E>vale la pena adoptarlo cuando tu toolchain soporta C++23 y la falla es ordinaria, local y esperada por callers.std::source_locationreemplaza muchos logging macros por un parámetro default tipado.
Fallas típicas y trade-offs
- Perseguir el estándar más nuevo antes de que el toolchain esté listo. El language mode, la standard library, el runtime de sanitizers y la plataforma target tienen que alinearse.
- Usar vocabulary types sin política.
optional,variant,expected, exceptions y assertions significan cosas distintas. Si un codebase los trata como intercambiables, los lectores pierden la señal. - Convertir views en referencias colgantes.
std::spanystd::string_viewno extienden lifetime. Devolver una view a un objeto local es un use-after-free más lindo. - Esconder allocation detrás de comodidad.
std::string,std::vector,std::function, coroutines y ranges pueden alocar o crecer estado según cómo los uses. - Tamaño de código de templates. Concepts mejoran diagnostics, pero cada instanciación template concreta todavía suma compile time y quizá tamaño binario.
- Asumir que release y debug se comportan parecido. Debug STL modes, inlining perdido e iterator checks pueden cambiar mucho el perfil.
En la práctica
Elegí un baseline de proyecto antes de elegir features. Para systems code hosted nuevo en 2026, C++20 es un piso práctico y C++23 es un objetivo de adopción feature por feature. Para kernels, bootloaders, firmware o trabajo de OS cross-compiled, el baseline es lo que tus headers freestanding, runtime, ABI y build chain puedan soportar de verdad.
Usá el "test de mecanismo" para cada feature. Preguntá qué object lifetime crea, qué data layout implica, si puede alocar, si puede throwear, si cruza un borde ABI y qué puede ver el optimizer. Si podés responder eso, Modern C++ sigue siendo low-level programming. Si no, la abstracción está manejando.
Conecta con: Move semantics y value categories · RAII: la idea que cambia todo · Templates y generic programming · La STL: containers, iterators, algorithms · Error handling: exceptions vs expected · Qué subset de C++ usar · Por qué leer assembly? Compiler Explorer
Fuentes
- Draft C++ Standard - working draft actual para chequear wording exacto de lenguaje y biblioteca cuando los resúmenes discrepan. https://eel.is/c++draft/
- cppreference - C++ compiler support - tabla práctica de disponibilidad de features entre compilers y standard libraries. https://en.cppreference.com/w/cpp/compiler_support
- cppreference - Constraints and concepts - mecanismo detrás de requirements de template con nombre y overloads constrained. https://en.cppreference.com/w/cpp/language/constraints
- cppreference -
std::span- detalla la view puntero-más-extent y sus implicancias de lifetime. https://en.cppreference.com/w/cpp/container/span - cppreference - Ranges library - referencia para range algorithms, views e iterator-category constraints de C++20. https://en.cppreference.com/w/cpp/ranges
- cppreference -
std::source_location- diagnostics tipados de call-site introducidos en C++20. https://en.cppreference.com/w/cpp/utility/source_location - C++ Core Guidelines - guía amplia de diseño para resource management, interfaces y estilo Modern C++. https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
- Compiler Explorer - inspeccioná si una abstracción moderna bajó al loop, call u object layout que esperabas. https://godbolt.org/