IceCream-CPP est une petite bibliothèque (en-tête unique) pour aider à la débogage d'impression dans C ++ 11 et en avant.
Essayez-le chez Compiler Explorer!
Contenu
Avec glace, une inspection d'exécution:
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;
}peut être codé à la place:
auto my_function ( int i, double d) -> void
{
IC ();
if (condition)
IC ();
else
IC ();
}et imprimera quelque chose comme:
ic| test.cpp:34 in "void my_function(int, double)"
ic| test.cpp:36 in "void my_function(int, double)"
De plus, toute inspection variable comme:
std::cout << " a: " << a
<< " , b: " << b
<< " , sum(a, b): " << sum(a, b)
<< std::endl;peut être simplifié à:
IC (a, b, sum(a, b));et imprimera:
ic| a: 7, b: 2, sum(a, b): 9
Nous pouvons également inspecter les données circulant dans un pipeline de vues de plage (à la fois STL Ranges et Range-V3), en insérant une fonction IC_V() au point d'intérêt:
auto rv = std::vector< int >{ 1 , 0 , 2 , 3 , 0 , 4 , 5 }
| vws::split( 0 )
| IC_V()
| vws::enumerate; De sorte que lorsque nous itrions sur rv , nous verrons l'impression:
ic| range_view_63:16[0]: [1]
ic| range_view_63:16[1]: [2, 3]
ic| range_view_63:16[2]: [4, 5]
Cette bibliothèque est inspirée de la bibliothèque de glace Python originale.
La glace-CPP est un fichier, une bibliothèque d'en-tête unique, ayant le STL comme seule dépendance. La façon la plus immédiate de l'utiliser est simplement de copier l'en-tête icecream.hpp dans votre projet.
Pour installer correctement le système informatique large, ainsi que les fichiers du projet CMake, exécutez ces commandes dans IceCream-CPP Project Root Directory:
mkdir build
cd build
cmake ..
cmake --install .Si vous utilisez Nix, IceCream-CPP peut être inclus comme entrée de flocons comme
inputs . icecream-cpp . url = "github:renatoGarcia/icecream-cpp" ; Le floke IceCream-CPP définit une superposition, afin qu'il puisse être utilisé lors de l'importation nixpkgs :
import nixpkgs {
system = "x86_64-linux" ;
overlays = [
icecream-cpp . overlays . default
] ;
} Ce faisant, une dérivation icecream-cpp sera ajoutée à l'ensemble d'attribut nixpkgs .
Un exemple fonctionnel de la façon d'utiliser IceCream-CPP dans un projet de flocons est ici.
Les versions publiées sont également disponibles sur Conan:
conan install icecream-cpp/0.3.1@Si vous utilisez Cmake:
find_package (IcecreamCpp)
include_directories ( ${IcecreamCpp_INCLUDE_DIRS} )Ajoutera le répertoire installé dans la liste des chemins d'inclusion.
Après avoir inclus l'en-tête icecream.hpp dans un fichier source:
# include < icecream.hpp > Toutes les fonctionnalités de la bibliothèque IceCream-CPP seront disponibles par les fonctions IC , IC_A et IC_V ; avec ses homologues respectifs IC_F , IC_FA et IC_FV ; qui se comportent de la même manière mais acceptent une chaîne de formatage de sortie comme son premier argument.
Le IC est le plus simple des fonctions de glace. S'il est appelé sans arguments, il imprimera le préfixe, le nom du fichier source, le numéro de ligne actuel et la signature de la fonction actuelle. Le code:
auto my_function ( int foo, double bar) -> void
{
// ...
IC ();
// ...
}Imprimera:
ic| test.cpp:34 in "void my_function(int, double)"
S'il est appelé avec des arguments, il imprimera le préfixe, ces noms d'arguments et ses valeurs. Le code:
auto v0 = std::vector< int >{ 1 , 2 , 3 };
auto s0 = std::string{ " bla " };
IC (v0, s0, 3.14 );Imprimera:
ic| v0: [1, 2, 3], s0: "bla", 3.14: 3.14
La variante IC_F se comporte de la même manière que la fonction IC , mais accepte une chaîne de formatage de sortie comme premier argument.
Pour imprimer les données circulant dans un pipeline de vues de plage (à la fois STL Ranges et Range-V3), nous utilisons la fonction IC_V , qui imprimera toute entrée qu'elle reçoit de la vue précédente. Étant donné que la fonction IC_V se trouve dans un pipeline de vues de plage, l'impression se fera paresseusement, tandis que chaque élément est généré. Par exemple:
namespace vws = std::views;
auto v0 = vws::iota( ' a ' ) | vws::enumerate | IC_V() | vws::take( 3 );
for ( auto e : v0)
{
// ...
} Dans ce code, rien ne sera imprimé lorsque v0 sera créé, juste au moment de l'itération. À chaque itération de la boucle for une ligne, une ligne sera imprimée, jusqu'à ce que nous ayons la sortie:
ic| range_view_61:53[0]: (0, 'a')
ic| range_view_61:53[1]: (1, 'b')
ic| range_view_61:53[2]: (2, 'c')
Note
IceCream-CPP essaiera de détecter si la bibliothèque Range-V3 est installée, et si c'est le cas, la prise en charge sera automatiquement activée. Cependant, lorsque vous utilisez C ++ 11 et C ++ 14, il y a une chance d'avoir une plage-V3 dans le système, mais la glace ne la trouve pas. Pour vous assurer que la prise en charge de Range-V3 est activée, définissez simplement la macro ICECREAM_RANGE_V3 avant d'inclure l'en-tête icecream.hpp
La fonction IC_V a deux paramètres facultatifs, IC_V(name, projection) .
Le nom de variable utilisé à la vue lors de l'impression. La disposition de l'impression est: <name>[<idx>]: <value> . Si le paramètre de nom n'est pas utilisé, la valeur par défaut à <name> est range_view_<source_location> .
Le code:
vws::iota ( ' a ' ) | vws::enumerate | IC_V( " foo " ) | vws::take( 2 );Une fois itéré, il est imprimé:
ic| foo[0]: (0, 'a')
ic| foo[1]: (1, 'b')
Un appelable qui recevra en tant qu'entrée les éléments de la vue précédente et doit renvoyer l'objet réel à imprimer.
Le code:
vws::iota ( ' a ' ) | vws::enumerate | IC_V([]( auto e){ return std::get< 1 >(e);}) | vws::take( 2 );Une fois itéré, il est imprimé:
ic| range_view_61:53[0]: 'a'
ic| range_view_61:53[1]: 'b'
Note
La fonction IC_V sera toujours transmise à la vue suivante un élément d'entrée inchangé, exactement comme il a été reçu de la vue précédente. Aucune action effectuée par la fonction projection aura un effet à ce sujet.
La variante IC_FV a le même comportement que la fonction IC_V , mais accepte une chaîne de formatage de sortie comme premier argument.
Sauf lorsqu'il est appelé avec exactement un argument, la fonction IC renverra un tuple avec tous ses arguments d'entrée. S'il est appelé avec un argument, il renverra l'argument lui-même.
Cela est fait de cette façon afin que vous puissiez utiliser IC pour inspecter un argument de fonction au point d'appel, sans autre changement de code. Dans le code:
my_function (IC(MyClass{})); L'objet MyClass sera transmis à my_function exactement comme si la fonction IC n'était pas là. La my_function continuera à recevoir une référence Rvalue à un objet MyClass .
Cette approche n'est cependant pas si pratique lorsque la fonction a de nombreux arguments. Sur le code:
my_function (IC(a), IC(b), IC(c), IC(d)); En plus d'écrire quatre fois la fonction IC , la sortie imprimée sera divisée en quatre lignes distinctes. Quelque chose comme:
ic| a: 1
ic| b: 2
ic| c: 3
ic| d: 4
Malheureusement, le simple fait d'envelopper les quatre arguments en un seul appel IC ne fonctionnera pas non plus. La valeur retournée sera un std:::tuple avec (a, b, c, d) et le my_function attend quatre arguments.
Pour contourner cela, il y a la fonction IC_A . IC_A se comporte exactement comme la fonction IC , mais reçoit un appelable comme premier argument, et l'appellera en utilisant tous les prochains arguments, les imprimant tous avant cela. Cet exemple de code précédent pourrait être réécrit comme:
IC_A (my_function, a, b, c, d);Et cette fois, il imprimera:
ic| a: 1, b: 2, c: 3, d: 4
La fonction IC_A renverra la même valeur que celle renvoyée par le callable. Le code:
auto mc = std::make_unique<MyClass>();
auto r = IC_A(mc->my_function, a, b);se comporte exactement comme:
auto mc = std::make_unique<MyClass>();
auto r = mc-> my_function (a, b); mais imprimera les valeurs de a et b .
La variante IC_FA se comporte de la même manière que la fonction IC_A , mais accepte une chaîne de formatage de sortie comme son premier argument, avant même l'argument appelable.
Il est possible de configurer comment la valeur doit être formatée lors de l'impression. Le code suivant:
auto a = int { 42 };
auto b = int { 20 };
IC_F ( " #X " , a, b);Imprimera:
ic| a: 0X2A, b: 0X14
Lorsque vous utilisez la variante IC_F au lieu de la Functio IC ordinaire. Un résultat similaire serait obtenu si vous utilisez respectivement IC_FA et IC_FV à la place d' IC_A et IC_V .
Lorsque vous utilisez les variantes de fonction de formatage ( IC_F et IC_FA ), la même chaîne de formatage sera appliquée par défaut à tous les arguments. Cela pourrait être un problème si nous souhaitons avoir des arguments avec un formatage distinct, ou si les arguments ont plusieurs types avec des syntaxes non valides mutuellement. Par conséquent, pour définir une chaîne de formatage distincte sur un argument spécifique, nous pouvons l'envelopper avec la fonction IC_ . Le code:
auto a = int { 42 };
auto b = int { 20 };
IC_F ( " #X " , a, IC_( " d " , b));Imprimera:
ic| a: 0X2A, b: 20
La fonction IC_ peut également être utilisée dans la fonction IC (ou IC_A ):
auto a = int { 42 };
auto b = int { 20 };
IC (IC_( " #x " , a), b);Imprimera:
ic| a: 0x2a, b: 20
Le dernier argument dans un appel de fonction IC_ est celui qui sera imprimé, tous les autres arguments qui ont précédé le dernier seront convertis en chaîne à l'aide de la fonction to_string et concaténés comme chaîne de formatage résultante.
auto a = float { 1.234 };
auto width = int { 7 };
IC (IC_( " *< " ,width, " .3 " , a)); Aura pour résultat une chaîne de formatage "*<7.3" , et imprimera:
ic| a: 1.23***
Juste pour l'exhaustivité dans les exemples, une utilisation d' IC_FA et IC_FV serait:
IC_FA ( " #x " , my_function, 10 , 20 );
auto rv0 = vws::iota( 0 ) | IC_FV( " [::2]:#x " , " bar " ) | vws::take( 5 );Cela imprimera:
ic| 10: 0xa, 20: 0x14
Et quand il itération sur rv0 :
ic| bar[0]: 0
ic| bar[2]: 0x2
ic| bar[4]: 0x4
Pour IC_F et IC_FA , la spécification de syntaxe des chaînes de formatage dépend à la fois du type T imprimé, et dans la stratégie d'impression de ce type utilisée par glace.
Pour IC_FV , la syntaxe de mise en forme si la chaîne de format de plage.
Le codage des caractères en C ++ est désordonné.
Les chaînes char8_t , char16_t et char32_t sont bien définies. Ils sont capables et contiennent respectivement des unités de code Unicode de 8, 16 et 32 bits, et ils sont codés dans UTF-8, UTF-16 et UTF-32 également respectivement.
Les chaînes char ont une taille de bit d'unité de code bien définie (donnée par CHAR_BIT , généralement 8 bits), mais il n'y a aucune exigence concernant son codage.
Les chaînes wchar_t n'ont ni une taille d'unité de code bien définie, ni aucune exigence concernant son encodage.
Dans un code comme celui-ci:
auto const str = std::string{ " foo " };
std::cout << str; Nous aurons trois points d'intérêt de trois personnages. Dans le premier, avant de compiler, ce code sera dans un fichier source dans un "codage source" non spécifié. Dans le deuxième point d'intérêt, le binaire compilé aura la chaîne "FOO" enregistrée dans un "codage d'exécution" non spécifié ". Enfin, sur le troisième point, le flux d'octet "FOO" reçu par std::cout sera finalement transmis au système, qui s'attend à ce que le flux soit codé dans un "codage de sortie" non spécifié non spécifié.
À partir de ces trois points d'intérêt de codage de caractère, à la fois "codage d'exécution" et "codage de sortie" ont un impact dans le fonctionnement interne de la crème glacée-CPP, et il n'y a aucun moyen de savoir avec certitude quel est le codage utilisé dans les deux. Face à cette incertitude, la stratégie adoptée consiste à offrir une fonction de transcodage par défaut raisonnable, qui tentera de convertir les données en codage droit et permettra à l'utilisateur d'utiliser sa propre implémentation en cas de besoin.
À l'exception des types de chaînes larges et Unicode (discutés ci-dessous), lors de l'impression de tout autre type, nous aurons ses données textuelles sérialisées dans "Encoding d'exécution". Ce "codage d'exécution" peut ou non être le même que le "codage de sortie", celui-ci étant le codage attendu par la sortie configurée. Pour cette raison, avant d'envoyer ces données à la sortie, nous devons les transcoder pour nous assurer que nous l'avons dans "Encodage de sortie". À cette fin, avant de livrer les données de texte à la sortie, nous l'envoyons à la fonction Output_Transcoder configurée, qui doit s'assurer qu'elle est codée dans le "codage de sortie" correct.
Lorsque vous imprimez les types de chaînes larges et Unicode, nous devons avoir un niveau de transcodage supplémentaire, car il est possible que les données de texte soient dans un codage de caractères distinct à partir du "codage d'exécution" attendu. Pour cette raison, une logique supplémentaire est appliquée pour s'assurer que les chaînes sont en "codage d'exécution" avant de les envoyer à la sortie. Ceci est discuté plus en détail dans des chaînes larges et des sections de chaînes Unicode.
Le système de configuration IceCream-CPP fonctionne "superposé par portée". Au niveau de base, nous avons l'objet global IC_CONFIG . Cette instance globale est partagée par l'ensemble du programme de course, comme on pourrait s'y attendre d'une variable globale. Il est créé avec toutes les options de configuration à ses valeurs par défaut, et tout changement est facilement vu par l'ensemble du programme.
À tout moment du code, nous pouvons créer une nouvelle couche de configuration à la portée actuelle en instanciant une nouvelle variable IC_CONFIG , appelant la macro IC_CONFIG_SCOPE() . Toutes les options de configuration de cette nouvelle instance seront dans un état "Unset" par défaut, et toute demande à une valeur d'option non encore définie sera déléguée à son parent. Cette demande augmentera sur la chaîne des parents jusqu'à ce que la première ayant cette option définisse les réponses.
Toutes les options de configuration sont définies en utilisant les méthodes d'accessoire de l'objet IC_CONFIG , et elles peuvent être enchaînées:
IC_CONFIG
.prefix( " ic: " )
.show_c_string( false )
.line_wrap_width( 70 ); IC_CONFIG est juste une variable régulière avec un nom drôle pour rendre une collision extrêmement improbable. Lors de l'appel d'une macro IC*(...) , il choisira l'instance IC_CONFIG sur SPOPE en faisant une recherche de nom non qualifiée, en utilisant les mêmes règles appliquées à toute autre variable régulière.
Pour résumer tout ce qui précède, dans le code:
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
} À la ligne A , la valeur de IC_CONFIG line_wrap_width , context_delimiter et show_c_string sera respectivement: 20 , "|" et false .
Après la clôture du bloc de portée le plus interne, à la ligne B , la valeur de line_wrap_width d' IC_CONFIG , context_delimiter et show_c_string sera respectivement: 20 , "|" et true .
Les opérations de lecture et d'écriture sur les objets IC_CONFIG sont sûres.
Note
Toute modification dans un IC_CONFIG , autre que à l'instance globale, ne sera vue que dans la portée actuelle. En conséquence, ces modifications ne se propageront pas à la portée de toute fonction appelée.
Activer ou désactiver la sortie de la macro IC(...) , activée par défaut.
auto is_enabled () const -> bool; auto enable () -> Config&;
auto disable () -> Config&;Le code:
IC ( 1 );
IC_CONFIG.disable();
IC ( 2 );
IC_CONFIG.enable();
IC ( 3 );Imprimera:
ic| 1: 1
ic| 3: 3
Ensembles où les données textuelles sérialisées seront imprimées. Par défaut, ces données seront imprimées sur la sortie d'erreur standard, la même que std::cerr .
auto output () const -> std::function<void(std::string const &)>; template < typename T>
auto output (T&& t) -> Config&; Où le type T peut être l'un des:
std::ostream .push_back(char) .*it = 'c'Par exemple, le code:
auto str = std::string{};
IC_CONFIG.output(str);
IC ( 1 , 2 ); Imprimera la sortie "ic| 1: 1, 2: 2n" sur la chaîne str .
Avertissement
ICECREAM-CPP ne s'appropriera pas l'argument t , donc les soins doivent être pris en charge par l'utilisateur pour s'assurer qu'il est vivant.
Une fonction qui génère le texte qui sera imprimé avant chaque sortie.
auto prefix () const -> std::function<std::string()>; template < typename ... Ts>
auto prefix (Ts&& ...values) -> Config&; Où les types Ts peuvent être l'un des:
T() -> U appelable, où U avez une surcharge d' operator<<(ostream&, U) .Le préfixe imprimé sera une concaténation de tous ces éléments.
Le code:
IC_CONFIG.prefix( " icecream| " );
IC ( 1 );
IC_CONFIG.prefix([]{ return 42 ;}, " - " );
IC ( 2 );
IC_CONFIG.prefix( " thread " , std::this_thread::get_id, " | " );
IC ( 3 );Imprimera:
icecream| 1: 1
42- 2: 2
thread 1 | 3: 3
Contrôles Si une variable char* doit être interprétée comme une chaîne C à terminaison nulle ( true ) ou un pointeur vers un char ( false ). La valeur par défaut est true .
auto show_c_string () const -> bool; auto show_c_string ( bool value) -> Config&;Le code:
char const * flavor = " mango " ;
IC_CONFIG.show_c_string( true );
IC (flavor);
IC_CONFIG.show_c_string( false );
IC (flavor);Imprimera:
ic| flavor: "mango";
ic| flavor: 0x55587b6f5410
Fonction qui transcode une chaîne wchar_t , d'un codage défini par le système à une chaîne char dans le système "Encoding d'exécution".
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&;Il n'y a aucune garantie que la chaîne d'entrée se terminera sur un terminateur nul (il s'agit du sémantique réel de String_view), donc l'utilisateur doit observer la valeur de taille de chaîne d'entrée.
L'implémentation par défaut vérifiera si le paramètre régional est défini sur une autre valeur que "C" ou "POSIX". Si oui, il transmettra l'entrée à la fonction STD :: WCRTomb. Sinon, il supposera que l'entrée est encodée Unicode (UTF-16 ou UTF-32, en conséquence de la taille des octets de wchar_t ), et l'a transcodée à UTF-8.
Fonction qui transcode une chaîne char32_t , d'un codage UTF-32 à une chaîne char dans le système "Encoding d'exécution".
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&;Il n'y a aucune garantie que la chaîne d'entrée se terminera sur un terminateur nul (il s'agit du sémantique réel de String_view), donc l'utilisateur doit observer la valeur de taille de chaîne d'entrée.
L'implémentation par défaut vérifiera que les paramètres régionaux C sont définis sur une autre valeur que "C" ou "POSIX". Si oui, il transmettra l'entrée à la fonction std :: c32rtomb. Sinon, il le fera juste transcoder à UTF-8.
Cette fonction sera utilisée pour transcoder toutes les chaînes char8_t , char16_t et char32_t . Lors du transcodage des chaînes char8_t et char16_t , elles seront d'abord converties en chaîne char32_t , avant d'être envoyées en entrée à cette fonction.
Fonction qui transcode une chaîne char , du système "Encoding d'exécution" à une chaîne char dans le système "Coding de sortie", comme prévu par la sortie configurée.
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&;Il n'y a aucune garantie que la chaîne d'entrée se terminera sur un terminateur nul (il s'agit du sémantique réel de String_view), donc l'utilisateur doit observer la valeur de taille de chaîne d'entrée.
L'implémentation par défaut suppose que le "codage d'exécution" est le même que le "codage de sortie" et renvoie simplement une entrée inchangée.
Le nombre maximum de caractères avant la sortie de la sortie sur plusieurs lignes. Valeur par défaut de 70 .
auto line_wrap_width () const -> std::size_t; auto line_wrap_width (std:: size_t value) -> Config&; Si le contexte (nom source, numéro de ligne et nom de fonction) doit être imprimé même lors de l'impression des variables. La valeur par défaut est false .
auto include_context () const -> bool; auto include_context ( bool value) -> Config&; La chaîne séparant le texte de contexte des valeurs des variables. La valeur par défaut est "- " .
auto context_delimiter () const -> std::string; auto context_delimiter (std::string const & value) -> Config&; Pour être imprimable, un type T doit satisfaire l'une des stratégies décrites dans les sections suivantes. S'il arrive que plusieurs stratégies soient satisfaites, celle avec la priorité plus élevée sera choisie.
La stratégie avec la plus haute préséance consiste à utiliser les E / S basées sur STL. Par conséquent, lors de l'impression d'un objet de type T , s'il existe un operator<<(ostream&, T) , il sera utilisé.
Les cordes C sont ambiguës. Une variable char* foo devrait-elle être interprétée comme un pointeur vers un seul char ou comme une chaîne à terminaison nulle? De même, la variable char bar[] est-elle un tableau de caractères uniques ou une chaîne à terminaison nulle? char baz[3] est-il un tableau avec trois caractères uniques ou est-ce une chaîne de taille deux plus un '