O ICECREAM-CPP é uma pequena biblioteca (um único cabeçalho) para ajudar na depuração da impressão no C ++ 11 e para a frente.
Experimente no Compiler Explorer!
Conteúdo
Com o sorvete, uma inspeção de execução:
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;
}pode ser codificado:
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)"
Além disso, qualquer inspeção variável como:
std::cout << " a: " << a
<< " , b: " << b
<< " , sum(a, b): " << sum(a, b)
<< std::endl;pode ser simplificado para:
IC (a, b, sum(a, b));e irá imprimir:
ic| a: 7, b: 2, sum(a, b): 9
Também podemos inspecionar os dados que fluem através de um pipeline de vistas de intervalo (faixas de STL e range-v3), inserindo uma função IC_V() no ponto de interesse:
auto rv = std::vector< int >{ 1 , 0 , 2 , 3 , 0 , 4 , 5 }
| vws::split( 0 )
| IC_V()
| vws::enumerate; Para que, quando iteramos no rv , veremos a impressão:
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 é inspirada na Biblioteca Python Icecream original.
O ICECREAM-CPP é um arquivo único, apenas biblioteca, com o STL como sua única dependência. A maneira mais imediata de usá -lo é apenas copiar o cabeçalho icecream.hpp para o seu projeto.
Para instalar corretamente o sistema de TI, juntamente com os arquivos do projeto CMake, execute esses comandos no diretório raiz do Projeto IceCream-CPP:
mkdir build
cd build
cmake ..
cmake --install .Se estiver usando o nix, o IceCream-CPP pode ser incluído como uma entrada de flocos como
inputs . icecream-cpp . url = "github:renatoGarcia/icecream-cpp" ; O flago IceCream-CPP define uma sobreposição, para que possa ser usada ao importar nixpkgs :
import nixpkgs {
system = "x86_64-linux" ;
overlays = [
icecream-cpp . overlays . default
] ;
} Fazendo isso, uma derivação icecream-cpp será adicionada ao conjunto de atributos nixpkgs .
Um exemplo de funcionamento de como usar o ICECREAM-CPP em um projeto de flocos está aqui.
As versões lançadas também estão disponíveis em Conan:
conan install icecream-cpp/0.3.1@Se estiver usando cmake:
find_package (IcecreamCpp)
include_directories ( ${IcecreamCpp_INCLUDE_DIRS} )Adicionará o diretório instalado na lista Incluir Paths.
Depois de incluir o cabeçalho icecream.hpp em um arquivo de origem:
# include < icecream.hpp > Todas as funcionalidades da biblioteca IceCream-CPP estarão disponíveis pelas funções IC , IC_A e IC_V ; Juntamente com seus respectivos contrapartes IC_F , IC_FA e IC_FV ; Isso se comporta o mesmo, mas aceita uma sequência de formatação de saída como seu primeiro argumento.
O IC é o mais simples das funções do sorvete. Se chamado sem argumentos, ele imprimirá o prefixo, o nome do arquivo de origem, o número da linha atual e a assinatura da função atual. O código:
auto my_function ( int foo, double bar) -> void
{
// ...
IC ();
// ...
}Imprimirá:
ic| test.cpp:34 in "void my_function(int, double)"
Se chamado com argumentos, ele imprimirá o prefixo, esses nomes de argumentos e seus valores. O 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
A variante IC_F se comporta da mesma forma que a função IC , mas aceita uma sequência de formatação de saída como seu primeiro argumento.
Para imprimir os dados que fluem através de um pipeline de vistas de intervalo (STL Ranges e Range-V3), usamos a função IC_V , que imprimirá qualquer entrada que ele receber da visualização anterior. Como a função IC_V está dentro de um pipeline de vistas de intervalo, a impressão será feita preguiçosamente, enquanto cada elemento é gerado. Por exemplo:
namespace vws = std::views;
auto v0 = vws::iota( ' a ' ) | vws::enumerate | IC_V() | vws::take( 3 );
for ( auto e : v0)
{
// ...
} Neste código, nada será impresso quando v0 for criado, exatamente quando o itera sobre ele. Em cada iteração na linha for LOOP ONE será impressa, até que tenhamos a saída:
ic| range_view_61:53[0]: (0, 'a')
ic| range_view_61:53[1]: (1, 'b')
ic| range_view_61:53[2]: (2, 'c')
Observação
O ICECREAM-CPP tentará detectar se a biblioteca Range-V3 estiver instalada e, em caso afirmativo, o suporte será ativado automaticamente. Ao usar C ++ 11 e C ++ 14, no entanto, há uma chance de ter Range-V3 no sistema, mas o sorvete não o encontra. Para garantir que o suporte para Range-V3 esteja ativado, basta definir o macro ICECREAM_RANGE_V3 antes de incluir o cabeçalho icecream.hpp
A função IC_V possui dois parâmetros opcionais, IC_V(name, projection) .
O nome da variável usado na visualização ao imprimir. O layout de impressão é: <name>[<idx>]: <value> . Se o parâmetro de nome não for usado, o valor padrão para <name> será range_view_<source_location> .
O código:
vws::iota ( ' a ' ) | vws::enumerate | IC_V( " foo " ) | vws::take( 2 );Quando iterado, imprimirá:
ic| foo[0]: (0, 'a')
ic| foo[1]: (1, 'b')
Um chamável que receberá como entrada os elementos da visualização anterior e deve retornar o objeto real a ser impresso.
O código:
vws::iota ( ' a ' ) | vws::enumerate | IC_V([]( auto e){ return std::get< 1 >(e);}) | vws::take( 2 );Quando iterado, imprimirá:
ic| range_view_61:53[0]: 'a'
ic| range_view_61:53[1]: 'b'
Observação
A função IC_V ainda será encaminhada para a próxima exibição, um elemento de entrada inalterado, exatamente como foi recebido da visualização anterior. Nenhuma ação realizada pela função projection terá algum efeito sobre isso.
A variante IC_FV tem o mesmo comportamento que a função IC_V , mas aceita uma sequência de formatação de saída como seu primeiro argumento.
Exceto quando chamado com exatamente um argumento, a função IC retornará uma tupla com todos os seus argumentos de entrada. Se chamado com um argumento, ele retornará o próprio argumento.
Isso é feito dessa maneira para que você possa usar IC para inspecionar um argumento de função no ponto de chamada, sem alterar mais código. No código:
my_function (IC(MyClass{})); O objeto MyClass será encaminhado para my_function exatamente o mesmo que se a função IC não estivesse lá. O my_function continuará recebendo uma referência de RValue a um objeto MyClass .
Essa abordagem, no entanto, não é tão prática quando a função tem muitos argumentos. No código:
my_function (IC(a), IC(b), IC(c), IC(d)); Além de escrever quatro vezes a função IC , a saída impressa será dividida em quatro linhas distintas. Algo como:
ic| a: 1
ic| b: 2
ic| c: 3
ic| d: 4
Infelizmente, apenas envolver todos os quatro argumentos em uma única chamada de IC também não funcionará. O valor retornado será um std:::tuple com (a, b, c, d) e a my_function espera quatro argumentos.
Para contornar isso, existe a função IC_A . IC_A se comporta exatamente como a função IC , mas recebe um chamável como seu primeiro argumento e o chamará usando todos os próximos argumentos, imprimindo todos eles antes disso. Esse código de exemplo anterior pode ser reescrito como:
IC_A (my_function, a, b, c, d);E desta vez ele imprimirá:
ic| a: 1, b: 2, c: 3, d: 4
A função IC_A retornará o mesmo valor que retornou pelo chamável. O código:
auto mc = std::make_unique<MyClass>();
auto r = IC_A(mc->my_function, a, b);se comporta exatamente o mesmo que:
auto mc = std::make_unique<MyClass>();
auto r = mc-> my_function (a, b); mas imprimirá os valores de a e b
A variante IC_FA se comporta da mesma forma que a função IC_A , mas aceita uma sequência de formatação de saída como seu primeiro argumento, mesmo antes do argumento chamável.
É possível configurar como o valor deve ser formatado durante a impressão. O seguinte código:
auto a = int { 42 };
auto b = int { 20 };
IC_F ( " #X " , a, b);Imprimirá:
ic| a: 0X2A, b: 0X14
Ao usar a variante IC_F em vez do Functio Plain IC . Um resultado semelhante seria obtido se o uso de IC_FA e IC_FV no lugar de IC_A e IC_V , respectivamente.
Ao usar as variantes da função de formatação ( IC_F e IC_FA ), a mesma sequência de formatação será aplicada por padrão a todos os argumentos. Isso pode ser um problema se desejarmos ter argumentos com formatação distinta, ou se os argumentos tiverem vários tipos com sintaxes não mutuamente válidas. Portanto, para definir uma sequência de formatação distinta como um argumento específico, podemos embrulhá -la com a função IC_ . O código:
auto a = int { 42 };
auto b = int { 20 };
IC_F ( " #X " , a, IC_( " d " , b));Imprimirá:
ic| a: 0X2A, b: 20
A função IC_ também pode ser usada na função Plain IC (ou IC_A ):
auto a = int { 42 };
auto b = int { 20 };
IC (IC_( " #x " , a), b);Imprimirá:
ic| a: 0x2a, b: 20
O último argumento em uma chamada de função IC_ é o que será impresso, todos os outros argumentos que vêm antes do último serão convertidos em uma string usando a função to_string e concatenados como a sequência de formatação resultante.
auto a = float { 1.234 };
auto width = int { 7 };
IC (IC_( " *< " ,width, " .3 " , a)); Terá como resultado uma string de formatação "*<7.3" e imprimirá:
ic| a: 1.23***
Apenas por completude nos exemplos, um uso de IC_FA e IC_FV seria:
IC_FA ( " #x " , my_function, 10 , 20 );
auto rv0 = vws::iota( 0 ) | IC_FV( " [::2]:#x " , " bar " ) | vws::take( 5 );Isso vai imprimir:
ic| 10: 0xa, 20: 0x14
e quando itera no rv0 :
ic| bar[0]: 0
ic| bar[2]: 0x2
ic| bar[4]: 0x4
Para IC_F e IC_FA , a especificação de sintaxe das cadeias de formatação depende tanto da impressão do tipo T quanto na estratégia de impressão desse tipo usada pelo IceCream.
Para IC_FV , a sintaxe de formatação se é a mesma que a string de formato de intervalo.
A codificação de caracteres em C ++ é confusa.
As cadeias de char8_t , char16_t e char32_t estão bem definidas. Eles são capazes e mantêm unidades de código Unicode de 8, 16 e 32 bits, respectivamente, e são codificadas em UTF-8, UTF-16 e UTF-32 também, respectivamente.
As cadeias de char têm um tamanho de bit de unidade de código bem definido (dado por CHAR_BIT , geralmente 8 bits), mas não há requisitos sobre sua codificação.
As cadeias wchar_t não têm um tamanho de unidade de código bem definido, nem requisitos sobre sua codificação.
Em um código como este:
auto const str = std::string{ " foo " };
std::cout << str; Teremos três pontos de interesse de codificação de caracteres. No primeiro, antes de compilar, esse código estará em um arquivo de origem em uma "codificação de origem" não especificada. No segundo ponto de interesse, o binário compilado terá a sequência "foo" salva em uma "codificação de execução" não especificada. Finalmente, no terceiro ponto, o fluxo de bytes "foo" recebido pelo std::cout será encaminhado para o sistema, que espera que o fluxo que seja codificado em uma "codificação de saída" também não especificada.
A partir desses três pontos de juros de codificação de caracteres, os "codificação de execução" e "codificação de saída" têm impacto no trabalho interno do IceCream-CPP, e não há como saber o que é a codificação usada em ambos. Em face dessa incerteza, a estratégia adotada é oferecer uma função de transcodificação padrão razoável, que tentará converter os dados na codificação correta e permitirá que o usuário use sua própria implementação quando necessário.
Exceto por tipos de string amplo e unicode (discutidos abaixo), ao imprimir qualquer outro tipo, teremos seus dados textuais serializados na "codificação de execução". Essa "codificação de execução" pode ou não ser a mesma que a "codificação de saída", sendo esta a codificação esperada pela saída configurada. Por esse motivo, antes de enviarmos esses dados para a saída, devemos transcodificar -os para garantir que o tenhamos na "codificação de saída". Para esse fim, antes de entregar os dados de texto para a saída, enviamos -os para a função configurada em output_transcoder, que deve garantir que ele esteja codificado na "codificação de saída" correta.
Ao imprimir os tipos de string amplo e unicode, precisamos ter mais um nível de transcodificação, porque é possível que os dados de texto estejam em um caractere distinto que codifica a partir da "codificação de execução" esperada. Por esse motivo, a lógica adicional é aplicada para garantir que as strings estejam na "codificação de execução" antes de enviá -las para a saída. Isso é discutido em cordas amplas e seções unicode strings.
O sistema de configuração do IceCream-CPP funciona "em camadas por escopo". No nível de base, temos o objeto Global IC_CONFIG . Essa instância global é compartilhada por todo o programa em execução, como seria de esperar de uma variável global. Ele é criado com todas as opções de configuração em seus valores padrão e qualquer alteração é prontamente vista por todo o programa.
Em qualquer ponto do código, podemos criar uma nova camada de configuração no escopo atual instantando uma nova variável IC_CONFIG , chamando a macro IC_CONFIG_SCOPE() . Todas as opções de configuração desta nova instância estarão em um estado "não definido" por padrão, e qualquer solicitação a um valor de opção ainda não definida será delegada ao seu pai. Essa solicitação subirá na cadeia pai até a primeira ter essa opção definir respostas.
Todas as opções de configuração são definidas usando métodos acessadores do objeto IC_CONFIG e podem ser encadeados:
IC_CONFIG
.prefix( " ic: " )
.show_c_string( false )
.line_wrap_width( 70 ); IC_CONFIG é apenas uma variável regular, com um nome engraçado para tornar uma colisão extremamente improvável. Ao ligar para qualquer macro IC*(...) , ele escolherá a instância IC_CONFIG no SCOPE, fazendo uma pesquisa de nome não qualificada, usando as mesmas regras aplicadas a qualquer outra variável regular.
Para resumir todas as opções acima, no 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
} Na linha A , o valor de linha IC_CONFIG line_wrap_width , context_delimiter e show_c_string serão respectivamente: 20 , "|" e false .
Após o fechamento do bloco de escopo mais interno, na linha B , o valor de linha IC_CONFIG line_wrap_width , context_delimiter e show_c_string serão respectivamente: 20 "|" , e true .
As operações de leitura e escrita nos objetos IC_CONFIG são seguras de threads.
Observação
Qualquer modificação em um IC_CONFIG , exceto na instância global, será vista apenas dentro do escopo atual. Como conseqüência, essas modificações não se propagam para o escopo de qualquer função chamada.
Ativar ou desativar a saída da macro IC(...) , ativado padrão.
auto is_enabled () const -> bool; auto enable () -> Config&;
auto disable () -> Config&;O código:
IC ( 1 );
IC_CONFIG.disable();
IC ( 2 );
IC_CONFIG.enable();
IC ( 3 );Imprimirá:
ic| 1: 1
ic| 3: 3
Define onde os dados textuais serializados serão impressos. Por padrão, que os dados serão impressos na saída de erro padrão, o mesmo que std::cerr .
auto output () const -> std::function<void(std::string const &)>; template < typename T>
auto output (T&& t) -> Config&; Onde o tipo T pode estar em:
std::ostream .push_back(char) .*it = 'c'Por exemplo, o código:
auto str = std::string{};
IC_CONFIG.output(str);
IC ( 1 , 2 ); Imprimirá a saída "ic| 1: 1, 2: 2n" na string str .
Aviso
O ICECREAM-CPP não assume a propriedade do argumento t , portanto, deve-se tomar cuidado pelo usuário para garantir que ele esteja vivo.
Uma função que gera o texto que será impresso antes de cada saída.
auto prefix () const -> std::function<std::string()>; template < typename ... Ts>
auto prefix (Ts&& ...values) -> Config&; Onde os tipos Ts podem estar em:
T() -> U , onde U tem uma sobrecarga de operator<<(ostream&, U) .O prefixo impresso será uma concatenação de todos esses elementos.
O 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 se uma variável char* deve ser interpretada como uma string c terminada nula ( true ) ou um ponteiro para um char ( false ). O valor padrão é true .
auto show_c_string () const -> bool; auto show_c_string ( bool value) -> Config&;O 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
Função que transcodifica uma string wchar_t , de uma codificação definida pelo sistema para uma string char no sistema "Encodificação de execução".
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&;Não há garantia de que a sequência de entrada termine em um terminador nulo (este é o semântico real do String_view), para que o usuário deve observar o valor do tamanho da string de entrada.
A implementação padrão verificará se a localidade C está definida como outro valor que não "C" ou "Posix". Se sim, encaminhará a entrada para a função STD :: WCRTomb. Caso contrário, assumirá que a entrada é a Unicode codificada (UTF-16 ou UTF-32, de acordo com o tamanho do byte de wchar_t ), e transcodificou-o para o UTF-8.
Função que transcodifica uma string char32_t , de uma codificação UTF-32 para uma string char no sistema "Encodificação de execução".
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&;Não há garantia de que a sequência de entrada termine em um terminador nulo (este é o semântico real do String_view), para que o usuário deve observar o valor do tamanho da string de entrada.
A implementação padrão verificará que a localidade C está definida como outro valor que não "C" ou "Posix". Se sim, encaminhará a entrada para a função STD :: C32RTomb. Caso contrário, ele apenas o transcodificará para o UTF-8.
Esta função será usada para transcodificar todas as cadeias de char8_t , char16_t e char32_t . Ao transcodificar as seqüências de caracteres char8_t e char16_t , elas serão convertidas primeiro em uma string char32_t , antes de serem enviadas como entrada para esta função.
Função que transcodifica uma string char , a partir do sistema "Execução codificando" para uma string char no sistema "Codificação de saída", conforme esperado pela saída 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&;Não há garantia de que a sequência de entrada termine em um terminador nulo (este é o semântico real do String_view), para que o usuário deve observar o valor do tamanho da string de entrada.
A implementação padrão pressupõe que a "codificação de execução" é a mesma que a "codificação de saída" e apenas retornará uma entrada inalterada.
O número máximo de caracteres antes que a saída seja quebrada em várias linhas. Valor padrão de 70 .
auto line_wrap_width () const -> std::size_t; auto line_wrap_width (std:: size_t value) -> Config&; Se o contexto (nome da fonte, número da linha e nome da função) deve ser impresso mesmo ao imprimir variáveis. O valor padrão é false .
auto include_context () const -> bool; auto include_context ( bool value) -> Config&; A sequência que separa o texto do contexto dos valores das variáveis. O valor padrão é "- " .
auto context_delimiter () const -> std::string; auto context_delimiter (std::string const & value) -> Config&; Para ser imprimível, um tipo T deve satisfazer uma das estratégias descritas nas próximas seções. Se acontecer que várias estratégias sejam satisfeitas, a precedência mais alta será escolhida.
A estratégia com a maior precedência é usar a E/S baseada em fluxo STL. Consequentemente, ao imprimir um objeto do Tipo T , se houver um operator<<(ostream&, T) , ele será usado.
C Strings são ambíguas. Uma variável char* foo deve ser interpretada como um ponteiro para um único char ou como uma corda terminada em nulo? Da mesma forma, a char bar[] é uma matriz de caracteres únicos ou uma string terminada em nulo? char baz[3] é uma matriz com três caracteres únicos ou é uma série de tamanho dois mais a '