ICECREAM-CPP es una pequeña biblioteca (encabezado único) para ayudar con la depuración impresa en C ++ 11 y hacia adelante.
¡Pruébelo en Compiler Explorer!
Contenido
Con ICECREAM, una inspección de ejecución:
auto my_function ( int i, double d) -> void
{
std::cout << " 1 " << std::endl;
if (condition)
std::cout << " 2 " << std::endl;
else
std::cout << " 3 " << std::endl;
}se puede codificar en su lugar:
auto my_function ( int i, double d) -> void
{
IC ();
if (condition)
IC ();
else
IC ();
}e imprimirá algo como:
ic| test.cpp:34 in "void my_function(int, double)"
ic| test.cpp:36 in "void my_function(int, double)"
Además, cualquier inspección variable como:
std::cout << " a: " << a
<< " , b: " << b
<< " , sum(a, b): " << sum(a, b)
<< std::endl;se puede simplificar a:
IC (a, b, sum(a, b));e imprimirá:
ic| a: 7, b: 2, sum(a, b): 9
También podemos inspeccionar los datos que fluyen a través de una tubería de vistas de rango (rangos STL y Range-V3), insertando una función IC_V() en el punto de interés:
auto rv = std::vector< int >{ 1 , 0 , 2 , 3 , 0 , 4 , 5 }
| vws::split( 0 )
| IC_V()
| vws::enumerate; Para que cuando iteremos en rv , veremos la impresión:
ic| range_view_63:16[0]: [1]
ic| range_view_63:16[1]: [2, 3]
ic| range_view_63:16[2]: [4, 5]
Esta biblioteca está inspirada en la biblioteca original de Python Icecream.
El IceCream-CPP es una biblioteca de un archivo, solo encabezado, que tiene el STL como su única dependencia. La forma más inmediata de usarlo es simplemente copiar el encabezado icecream.hpp en su proyecto.
Para instalar correctamente el sistema de TI, junto con los archivos del proyecto CMake, ejecute estos comandos en el directorio root del proyecto ICECREAM-CPP:
mkdir build
cd build
cmake ..
cmake --install .Si usa NIX, ICECREAM-CPP se puede incluir como una entrada de copos como copos como
inputs . icecream-cpp . url = "github:renatoGarcia/icecream-cpp" ; El copo ICECREAM-CPP define una superposición, para que pueda usarse al importar nixpkgs :
import nixpkgs {
system = "x86_64-linux" ;
overlays = [
icecream-cpp . overlays . default
] ;
} Al hacer esto, se agregará una derivación de icecream-cpp al conjunto de atributos nixpkgs .
Aquí hay un ejemplo de trabajo de cómo usar ICECREAM-CPP en un proyecto de escamas.
Las versiones publicadas también están disponibles en Conan:
conan install icecream-cpp/0.3.1@Si usa cmake:
find_package (IcecreamCpp)
include_directories ( ${IcecreamCpp_INCLUDE_DIRS} )Agregará el directorio instalado dentro de la lista de rutas de incluido.
Después de incluir el encabezado icecream.hpp en un archivo fuente:
# include < icecream.hpp > Todas las funcionalidades de la biblioteca ICECREAM-CPP estarán disponibles por las funciones IC , IC_A e IC_V ; junto con sus respectivas contrapartes IC_F , IC_FA e IC_FV ; Eso se comporta igual pero acepta una cadena de formato de salida como su primer argumento.
El IC es la más simple de las funciones de IceCream. Si se llama sin argumentos, imprimirá el prefijo, el nombre del archivo de origen, el número de línea actual y la firma de la función actual. El código:
auto my_function ( int foo, double bar) -> void
{
// ...
IC ();
// ...
}imprimirá:
ic| test.cpp:34 in "void my_function(int, double)"
Si se les llama con argumentos, imprimirá el prefijo, esos argumentos nombres y sus valores. El código:
auto v0 = std::vector< int >{ 1 , 2 , 3 };
auto s0 = std::string{ " bla " };
IC (v0, s0, 3.14 );imprimirá:
ic| v0: [1, 2, 3], s0: "bla", 3.14: 3.14
La variante IC_F se comporta lo mismo que la función IC , pero acepta una cadena de formato de salida como su primer argumento.
Para imprimir los datos que fluyen a través de una tubería de vista de rango (tanto rangos STL como Range-V3), utilizamos la función IC_V , que imprimirá cualquier entrada que reciba desde la vista anterior. Dado que la función IC_V está dentro de una tubería de vista de rango, la impresión se realizará perezosamente, mientras se genera cada elemento. Por ejemplo:
namespace vws = std::views;
auto v0 = vws::iota( ' a ' ) | vws::enumerate | IC_V() | vws::take( 3 );
for ( auto e : v0)
{
// ...
} En este código, no se imprimirá nada cuando se cree v0 , solo cuando se presenta sobre él. En cada iteración en la línea for Loop One se imprimirá, hasta que tengamos la salida:
ic| range_view_61:53[0]: (0, 'a')
ic| range_view_61:53[1]: (1, 'b')
ic| range_view_61:53[2]: (2, 'c')
Nota
ICECREAM-CPP intentará detectar si la biblioteca Range-V3 está instalada y, de ser así, el soporte se habilitará automáticamente. Sin embargo, cuando se usa C ++ 11 y C ++ 14, existe la posibilidad de tener Range-V3 en el sistema, pero ICECREAM no lo encuentra. Para asegurarse de que el soporte a Range-V3 esté habilitado, simplemente defina el Macro ICECREAM_RANGE_V3 antes de incluir el encabezado icecream.hpp
La función IC_V tiene dos parámetros opcionales, IC_V(name, projection) .
El nombre de la variable utilizado para la vista al imprimir. El diseño de impresión es: <name>[<idx>]: <value> . Si no se usa el parámetro de nombre, el valor predeterminado a <name> es range_view_<source_location> .
El código:
vws::iota ( ' a ' ) | vws::enumerate | IC_V( " foo " ) | vws::take( 2 );Cuando se iterated se imprimirá:
ic| foo[0]: (0, 'a')
ic| foo[1]: (1, 'b')
Un llamado que recibirá como entrada los elementos de la vista anterior y debe devolver el objeto real que se imprima.
El código:
vws::iota ( ' a ' ) | vws::enumerate | IC_V([]( auto e){ return std::get< 1 >(e);}) | vws::take( 2 );Cuando se iterated se imprimirá:
ic| range_view_61:53[0]: 'a'
ic| range_view_61:53[1]: 'b'
Nota
La función IC_V aún se reenviará a la siguiente vista de un elemento de entrada sin cambios, exactamente como se recibió de la vista anterior. Ninguna acción realizada por la función projection tendrá ningún efecto en eso.
La variante IC_FV tiene el mismo comportamiento que la función IC_V , pero acepta una cadena de formato de salida como su primer argumento.
Excepto cuando se llama exactamente con un argumento, la función IC devolverá una tupla con todos sus argumentos de entrada. Si se llama con un argumento, devolverá el argumento en sí.
Esto se hace de esta manera para que pueda usar IC para inspeccionar un argumento de función en el punto de llamada, sin más cambio de código. En el código:
my_function (IC(MyClass{})); El objeto MyClass se reenviará a my_function exactamente igual que si la función IC no estuviera allí. La my_function continuará recibiendo una referencia de RValue a un objeto MyClass .
Sin embargo, este enfoque no es tan práctico cuando la función tiene muchos argumentos. En el código:
my_function (IC(a), IC(b), IC(c), IC(d)); Además de escribir cuatro veces la función IC , la salida impresa se dividirá en cuatro líneas distintas. Algo como:
ic| a: 1
ic| b: 2
ic| c: 3
ic| d: 4
Desafortunadamente, solo envolver los cuatro argumentos en una sola llamada IC tampoco funcionará. El valor devuelto será un std:::tuple con (a, b, c, d) y la my_function espera cuatro argumentos.
Para trabajar alrededor de eso, está la función IC_A . IC_A se comporta exactamente como la función IC , pero recibe un argumento de llamado como primer argumento, y lo llamará usando todos los próximos argumentos, imprimiéndolos todos antes de eso. Ese código de ejemplo anterior podría reescribirse como:
IC_A (my_function, a, b, c, d);Y esta vez se imprimirá:
ic| a: 1, b: 2, c: 3, d: 4
La función IC_A devolverá el mismo valor que se devuelve por la llamada. El código:
auto mc = std::make_unique<MyClass>();
auto r = IC_A(mc->my_function, a, b);se comporta exactamente igual que:
auto mc = std::make_unique<MyClass>();
auto r = mc-> my_function (a, b); pero imprimirá los valores de a y b
La variante IC_FA se comporta de la misma manera que la función IC_A , pero acepta una cadena de formato de salida como su primer argumento, incluso antes del argumento invocado.
Es posible configurar cómo el valor debe formatearse mientras se imprime. El siguiente código:
auto a = int { 42 };
auto b = int { 20 };
IC_F ( " #X " , a, b);imprimirá:
ic| a: 0X2A, b: 0X14
Cuando se usa la variante IC_F en lugar del IC Funcctio. Se obtendría un resultado similar si use IC_FA e IC_FV en lugar de IC_A e IC_V respectivamente.
Al usar las variantes de la función de formato ( IC_F e IC_FA ), la misma cadena de formato se aplicará de forma predeterminada a todos los argumentos. Eso podría ser un problema si deseamos tener argumentos con formato distinto, o si los argumentos tienen múltiples tipos con sintaxis no mutuamente válidas. Por lo tanto, para establecer una cadena de formato distinta en un argumento específico, podemos envolverla con la función IC_ . El código:
auto a = int { 42 };
auto b = int { 20 };
IC_F ( " #X " , a, IC_( " d " , b));imprimirá:
ic| a: 0X2A, b: 20
La función IC_ también se puede usar dentro de la función IC (o IC_A ) simple:
auto a = int { 42 };
auto b = int { 20 };
IC (IC_( " #x " , a), b);imprimirá:
ic| a: 0x2a, b: 20
El último argumento en una llamada de función IC_ es el que se imprimirá, todos los demás argumentos que vengan antes que el último se convertirán en una cadena utilizando la función to_string y concatenadas como la cadena de formato resultante.
auto a = float { 1.234 };
auto width = int { 7 };
IC (IC_( " *< " ,width, " .3 " , a)); Tendrá como resultado una cadena de formato "*<7.3" , e imprimirá:
ic| a: 1.23***
Solo para completar en los ejemplos, un uso de IC_FA e IC_FV sería:
IC_FA ( " #x " , my_function, 10 , 20 );
auto rv0 = vws::iota( 0 ) | IC_FV( " [::2]:#x " , " bar " ) | vws::take( 5 );Esto imprimirá:
ic| 10: 0xa, 20: 0x14
y al iterando en rv0 :
ic| bar[0]: 0
ic| bar[2]: 0x2
ic| bar[4]: 0x4
A IC_F e IC_FA , la especificación de sintaxis de las cadenas de formato depende tanto del tipo T como en la estrategia de impresión de ese tipo utilizada por ICECREAM.
A IC_FV , la sintaxis de formato si es lo mismo que la cadena de formato de rango.
La codificación de personajes en C ++ es desordenada.
Las cadenas char8_t , char16_t y char32_t están bien definidas. Son capaces y contienen unidades de código Unicode de 8, 16 y 32 bits respectivamente, y están codificadas en UTF-8, UTF-16 y UTF-32 también respectivamente.
Las cadenas char tienen un tamaño de bit de la unidad de código bien definido (dado por CHAR_BIT , generalmente 8 bits), pero no hay requisitos sobre su codificación.
Las cadenas wchar_t no tienen un tamaño de unidad de código bien definido, ni ningún requisito sobre su codificación.
En un código como este:
auto const str = std::string{ " foo " };
std::cout << str; Tendremos tres puntos de codificación de interés de personajes. En el primero, antes de compilar, ese código estará en un archivo fuente en una "codificación de origen" no especificada. En el segundo punto de interés, el binario compilado tendrá la cadena "Foo" guardada en una "codificación de ejecución" no especificada. Finalmente, en el tercer punto, el flujo de bytes "Foo" recibido por std::cout se reenviará en última instancia al sistema, lo que espera que la corriente se codifique en una "codificación de salida" no especificada.
A partir de esos tres puntos de interés de la codificación de carácter, tanto la "codificación de ejecución" como la "codificación de salida" tienen un impacto en el funcionamiento interno de IceCream-CPP, y no hay forma de saber con certeza cuál es la codificación utilizada en ambos. Ante esta incertidumbre, la estrategia adoptada es ofrecer una función de transcodificación predeterminada razonable, que intentará convertir los datos en la codificación correcta y permitir al usuario usar su propia implementación cuando sea necesario.
A excepción de los tipos de cadena Wide y Unicode (que se analiza a continuación), al imprimir cualquier otro tipo, tendremos sus datos textuales serializados en "Codificación de ejecución". Que la "codificación de ejecución" puede o no ser lo mismo que la "codificación de salida", siendo esta la codificación esperada por la salida configurada. Debido a eso, antes de enviar esos datos a la salida, debemos transcelarlos para asegurarnos de que lo tengamos en "codificación de salida". Con ese fin, antes de entregar los datos de texto a la salida, los enviamos a la función de salida_transcoder configurada, que debe asegurarse de que esté codificado en la "codificación de salida" correcta.
Al imprimir los tipos de cadenas Wide y Unicode, necesitamos tener un nivel de transcodificación más, porque es posible que los datos de texto estén en un carácter distinto que codifica la "codificación de ejecución" esperada. Debido a eso, se aplica una lógica adicional para asegurarse de que las cadenas estén en "codificación de ejecución" antes de enviarlas a la salida. Esto se discute más a fondo en cadenas anchas y secciones de cadenas unicode.
El sistema de configuración ICECREAM-CPP funciona "capas por alcance". En el nivel base tenemos el objeto IC_CONFIG global. Esa instancia global es compartida por todo el programa de ejecución, como se esperaría de una variable global. Se crea con todas las opciones de configuración en sus valores predeterminados, y cualquier cambio es visto fácilmente por todo el programa.
En cualquier punto del código, podemos crear una nueva capa de configuración en el alcance actual instanciando una nueva variable IC_CONFIG , llamando a la macro IC_CONFIG_SCOPE() . Todas las opciones de configuración de esta nueva instancia estarán en un estado "unset" de forma predeterminada, y cualquier solicitud a un valor de opción aún no establecido se delegará a su padre. Esa solicitud aumentará en la cadena principal hasta que la primera que tenga esa opción establece respuestas.
Todas las opciones de configuración se establecen mediante el uso de métodos de accesorios del objeto IC_CONFIG , y se pueden encadenar:
IC_CONFIG
.prefix( " ic: " )
.show_c_string( false )
.line_wrap_width( 70 ); IC_CONFIG es solo una variable regular con un nombre divertido para que una colisión sea extremadamente improbable. Al llamar a cualquier macro IC*(...) , elegirá la instancia IC_CONFIG en el alcance haciendo una búsqueda de nombre no calificada, utilizando las mismas reglas aplicadas a cualquier otra variable regular.
Para resumir todo lo anterior, en el código:
auto my_function () -> void
{
IC_CONFIG. line_wrap_width ( 20 );
IC_CONFIG_SCOPE ();
IC_CONFIG. context_delimiter ( " | " );
IC_CONFIG. show_c_string ( true );
{
IC_CONFIG_SCOPE ();
IC_CONFIG. show_c_string ( false );
// A
}
// B
} En la línea A , el valor de IC_CONFIG 's line_wrap_width , context_delimiter y show_c_string será respectivamente: 20 , "|" y false .
Después del cierre del bloque de alcance más interno, en la línea B , el valor de IC_CONFIG 's line_wrap_width , context_delimiter y show_c_string será respectivamente: 20 , "|" , y true .
Las operaciones de lectura y escritura en los objetos IC_CONFIG son seguros.
Nota
Cualquier modificación en un IC_CONFIG , que no sea la instancia global, se verá solo dentro del alcance actual. Como consecuencia, esas modificaciones no se propagarán al alcance de ninguna función llamada.
Habilitar o deshabilitar la salida de la macro IC(...) , habilitado por defecto.
auto is_enabled () const -> bool; auto enable () -> Config&;
auto disable () -> Config&;El código:
IC ( 1 );
IC_CONFIG.disable();
IC ( 2 );
IC_CONFIG.enable();
IC ( 3 );imprimirá:
ic| 1: 1
ic| 3: 3
Establece donde se imprimirán los datos textuales serializados. Por defecto, esos datos se imprimirán en la salida de error estándar, lo mismo que std::cerr .
auto output () const -> std::function<void(std::string const &)>; template < typename T>
auto output (T&& t) -> Config&; Donde el tipo T puede ser cualquiera de:
std::ostream .push_back(char) .*it = 'c'Por ejemplo, el código:
auto str = std::string{};
IC_CONFIG.output(str);
IC ( 1 , 2 ); Imprimirá la salida "ic| 1: 1, 2: 2n" en la cadena str .
Advertencia
ICECREAM-CPP no tomará la propiedad del argumento t , por lo que el usuario debe tener cuidado para asegurarse de que esté vivo.
Una función que genere el texto que se imprimirá antes de cada salida.
auto prefix () const -> std::function<std::string()>; template < typename ... Ts>
auto prefix (Ts&& ...values) -> Config&; Donde los tipos Ts pueden ser cualquiera de:
T() -> U U donde tiene una sobrecarga de operator<<(ostream&, U) .El prefijo impreso será una concatenación de todos esos elementos.
El código:
IC_CONFIG.prefix( " icecream| " );
IC ( 1 );
IC_CONFIG.prefix([]{ return 42 ;}, " - " );
IC ( 2 );
IC_CONFIG.prefix( " thread " , std::this_thread::get_id, " | " );
IC ( 3 );imprimirá:
icecream| 1: 1
42- 2: 2
thread 1 | 3: 3
Controles si una variable char* debe interpretarse como una cadena C nula terminada ( true ) o un puntero a un char ( false ). El valor predeterminado es true .
auto show_c_string () const -> bool; auto show_c_string ( bool value) -> Config&;El código:
char const * flavor = " mango " ;
IC_CONFIG.show_c_string( true );
IC (flavor);
IC_CONFIG.show_c_string( false );
IC (flavor);imprimirá:
ic| flavor: "mango";
ic| flavor: 0x55587b6f5410
Función que transcoda una cadena wchar_t , desde una codificación definida del sistema a una cadena char en el sistema "Codificación de ejecución".
auto wide_string_transcoder () const -> std::function<std::string( wchar_t const *, std:: size_t )>; auto wide_string_transcoder (std::function<std::string( wchar_t const *, std:: size_t )> transcoder) -> Config&;
auto wide_string_transcoder (std::function<std::string(std::wstring_view)> transcoder) -> Config&;No hay garantía de que la cadena de entrada finalice en un terminador nulo (este es el semántico real de String_View), por lo que el usuario debe observar el valor del tamaño de la cadena de entrada.
La implementación predeterminada verificará si la configuración regional C se establece en otro valor que "C" o "Posix". En caso afirmativo, reenviará la entrada a la función STD :: WCRTOMM. De lo contrario, supondrá que la entrada está codificada por Unicode (UTF-16 o UTF-32, en consecuencia al tamaño de bytes de wchar_t ), y la transcodifica a UTF-8.
Función que transcoda una cadena char32_t , de una codificación UTF-32 a una cadena char en el sistema "Codificación de ejecución".
auto unicode_transcoder () const -> std::function<std::string( char32_t const *, std:: size_t )>; auto unicode_transcoder (std::function<std::string( char32_t const *, std:: size_t )> transcoder) -> Config&;
auto unicode_transcoder (std::function<std::string(std::u32string_view)> transcoder) -> Config&;No hay garantía de que la cadena de entrada finalice en un terminador nulo (este es el semántico real de String_View), por lo que el usuario debe observar el valor del tamaño de la cadena de entrada.
La implementación predeterminada verificará la configuración regional C en otro valor que "C" o "Posix". En caso afirmativo, reenviará la entrada a la función STD :: C32RTOMM. De lo contrario, simplemente lo transcodificará a UTF-8.
Esta función se utilizará para transcodificar todas las cadenas char8_t , char16_t y char32_t . Al transcodificar las cadenas char8_t y char16_t , primero se convertirán en una cadena char32_t , antes de ser enviada como entrada a esta función.
Función que transcoda una cadena char , desde el sistema "codificación de ejecución" a una cadena char en la "codificación de salida" del sistema, como se esperaba por la salida configurada.
auto output_transcoder () const -> std::function<std::string( char const *, std:: size_t )>; auto output_transcoder (std::function<std::string( char const *, std:: size_t )> transcoder) -> Config&;
auto output_transcoder (std::function<std::string(std::string_view)> transcoder) -> Config&;No hay garantía de que la cadena de entrada finalice en un terminador nulo (este es el semántico real de String_View), por lo que el usuario debe observar el valor del tamaño de la cadena de entrada.
La implementación predeterminada supone que la "codificación de ejecución" es la misma que la "codificación de salida", y solo devolverá una entrada sin cambios.
El número máximo de caracteres antes de que la salida se rompa en varias líneas. Valor predeterminado de 70 .
auto line_wrap_width () const -> std::size_t; auto line_wrap_width (std:: size_t value) -> Config&; Si el contexto (nombre de origen, número de línea y nombre de función) debe imprimirse incluso al imprimir variables. El valor predeterminado es false .
auto include_context () const -> bool; auto include_context ( bool value) -> Config&; La cadena que separa el texto de contexto de los valores de variables. El valor predeterminado es "- " .
auto context_delimiter () const -> std::string; auto context_delimiter (std::string const & value) -> Config&; Para ser imprimible, un tipo T debe satisfacer una de las estrategias descritas en las siguientes secciones. Si sucede que se satisfacen múltiples estrategias, se eligirá la de mayor precedencia.
La estrategia con la mayor precedencia es usar la E/S basada en la corriente STL. En consecuencia, al imprimir un objeto de Tipo T , si existe un operator<<(ostream&, T) , se utilizará.
Las cadenas C son ambiguas. ¿Debería interpretarse una variable char* foo como un puntero a un solo char o como una cadena terminada nula? Del mismo modo, ¿es la variable char bar[] una matriz de caracteres individuales o una cadena terminada nula? ¿Es char baz[3] una matriz con tres caracteres individuales o es una cadena de tamaño dos más un '