Hola Mundo, de nuevo. En una publicación anterior ya mencionaba cómo hacer un ¡Hola Mundo! en C++. Se ha introducido una característica en el estándar C++23 que modifica la forma en la que se escribe ese primer programa que sirve como ejercicio de introducción a un lenguaje de programación, o que se escribe como prueba inicial al actualizar el sistema operativo, al usar otro compilador, una nueva máquina, al probar una biblioteca, una nueva IDE, al probar pipelines en CI/CD, etc.

std::print

Así como sucede con la mayoría de funcionalidades en C++ hay diversas formas de escribir mensajes en el stdout, tales como printf, std::puts(), y std::cout.
A partir del estándar C++23 se puede encontrar la funcionalidad std::print() en las siguientes versiones de la biblioteca estándar:

Biblioteca Versión
gcc libstdc++ 14
clang libc++ 18
msvc stl 19.37

std::print es una funcionalidad que viene de la ya conocida biblioteca {fmt}1, que ya tenía integradas las funcionalidades de formato std::format.

Para usar las funcionalidades basta con usar un compilador con versión compatible, e indicarle que se usa el estándar -std=c++23.

Sintaxis y Mini-lenguaje

Las características de formato se pueden ver en la tabla de mini-language2 de la biblioteca {fmt}, y se refiere a las especificaciones de formato soportadas. Como especificaciones destacadas se encuentran las facilidades para el formato binario, hexadecimal, de punto-flotante, de chrono, y de rangos.

La sintaxis se da por medio de campos de reemplazo y argumentos posicionales envueltos en llaves {}.

   
campo de reemplazo '{' [arg_id] [':' (formato)] '}'
arg_id identificador del argumento
formato formato de la cadena
ejemplo std::print("{0:d}", 42);

Hola de nuevo

Un nuevo “Hola Mundo”, en comparación con los anteriores, podría verse de la siguiente manera:

Otros ejemplos

En una publicación anterior está más detallada la funcionalidad de {fmt} , y aunque la implementación de std::print puede variar dependiendo del compilador, sus funcionalidades son las mismas. El ejemplo de algunos especificadores de formato luce así:

#include <print>

std::print("{0:<6} = {1:#06x} {3:f} {2:} {4:#b} {4:d}\n", 
            "texto", 0xff, 42, 42.0, 0b10);
// Resultado: texto  = 0x00ff 42.000000 42 0b10 2

Explicación

  • El argumento 0 está alineado a la izquierda con ancho de 6 caracteres, y contiene la cadena texto
  • El argumento 1 es hexadecimal con prefijo (0x) de ancho 6 rellenado con ceros
  • El argumento 3 aparece antes que el argumento 2. Contiene la variable tipo double con valor 42, con precisión estándar de 6 caracteres, con formato de punto flotante
  • El argumento 2 aparece después que el argumento 3. Contiene un entero
  • El argumento 4 aparece dos veces, la primera es en representación binaria con prefijo (0b), la segunda es en representación decimal

Motivación

Hay diversos motivos por los cuales std::print llama la atención. Una de sus características más interesantes es la validación de tipos de datos en tiempo de compilación, que incluso tiene detección de errores por medio de análisis estático.

Es fácilmente extensible, es compatible con los contenedores de la STL, con la biblioteca de rangos, con la biblioteca de fechas std::chrono, y con tipos de datos definidos por usuarios. Es compatible con Unicode, tiene un gran desempeño según sus pruebas de referencia benchmark3, y funciona bien con multi-hilos. Tiene una sintaxis mucho más sencilla que la de iostreams, y cubre con las falencias de printf sin afectar el desempeño.

Algunos temas relacionados que permiten entender un poco más del funcionamiento de std::printf incluyen plantillas variádicas (paquetes de parámetros)4, perfect forwarding5, y argumentos posicionales.

Fuentes


  1. Latest {fmt} syntax  

  2. Format Specification Mini-Language 

  3. {fmt} benchmarks 

  4. Paquetes de parámetros 

  5. Effective Modern C++, capítulo 5. R-Value References, Move Semantics And Perfect Forwarding 

Deja un comentario