IceCream-CPPは、C ++ 11以降での印刷デバッグを支援するための小さな(シングルヘッダー)ライブラリです。
コンパイラエクスプローラーで試してみてください!
コンテンツ
IceCreamを使用して、実行検査:
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;
}代わりにコード化できます:
auto my_function ( int i, double d) -> void
{
IC ();
if (condition)
IC ();
else
IC ();
}そして、次のようなものを印刷します
ic| test.cpp:34 in "void my_function(int, double)"
ic| test.cpp:36 in "void my_function(int, double)"
また、次のような変数検査
std::cout << " a: " << a
<< " , b: " << b
<< " , sum(a, b): " << sum(a, b)
<< std::endl;次のことを簡素化できます。
IC (a, b, sum(a, b));そして印刷します:
ic| a: 7, b: 2, sum(a, b): 9
また、関心のある時点でIC_V()関数を挿入することにより、範囲ビューパイプライン(STL範囲と範囲-V3の両方)を介して流れるデータを検査することもできます。
auto rv = std::vector< int >{ 1 , 0 , 2 , 3 , 0 , 4 , 5 }
| vws::split( 0 )
| IC_V()
| vws::enumerate; rvを反復すると、印刷が表示されます。
ic| range_view_63:16[0]: [1]
ic| range_view_63:16[1]: [2, 3]
ic| range_view_63:16[2]: [4, 5]
このライブラリは、元のPython IceCreamライブラリに触発されています。
IceCream-CPPは、STLをその唯一の依存関係として持つ1つのファイル、ヘッダーのみのライブラリです。それを使用する最も差し迫った方法は、 icecream.hppヘッダーをプロジェクトにコピーすることです。
CMAKEプロジェクトファイルとともにシステムを広くインストールするには、ICECREAM-CPPプロジェクトルートディレクトリでこれらのコマンドを実行します。
mkdir build
cd build
cmake ..
cmake --install .nixを使用している場合、Icecream-cppはフレーク入力として含めることができます
inputs . icecream-cpp . url = "github:renatoGarcia/icecream-cpp" ; IceCream-CPPフレークはオーバーレイを定義しているため、 nixpkgsをインポートするときに使用できます。
import nixpkgs {
system = "x86_64-linux" ;
overlays = [
icecream-cpp . overlays . default
] ;
}これを行うと、 icecream-cpp派生がnixpkgs属性セットに追加されます。
フレークプロジェクトでIcecream-CPPを使用する方法の実用的な例はこちらです。
リリースバージョンはコナンでも入手できます。
conan install icecream-cpp/0.3.1@cmakeを使用する場合:
find_package (IcecreamCpp)
include_directories ( ${IcecreamCpp_INCLUDE_DIRS} )インクルードパスリスト内にインストールされたディレクトリを追加します。
ソースファイルにicecream.hppヘッダーを含めた後:
# include < icecream.hpp > ICECREAM-CPPライブラリのすべての機能は、関数IC 、 IC_A 、およびIC_Vによって利用可能になります。それぞれのカウンターパートIC_F 、 IC_FA 、およびIC_FVと一緒に。それは同じように振る舞いますが、出力フォーマット文字列を最初の引数として受け入れます。
IC 、ICECREAM関数の中で最も単純なものです。引数なしで呼び出された場合、プレフィックス、ソースファイル名、現在の行番号、および現在の関数の署名を印刷します。コード:
auto my_function ( int foo, double bar) -> void
{
// ...
IC ();
// ...
}印刷します:
ic| test.cpp:34 in "void my_function(int, double)"
引数で呼び出されると、プレフィックス、これらの引数名、およびその値が印刷されます。コード:
auto v0 = std::vector< int >{ 1 , 2 , 3 };
auto s0 = std::string{ " bla " };
IC (v0, s0, 3.14 );印刷します:
ic| v0: [1, 2, 3], s0: "bla", 3.14: 3.14
バリアントIC_F IC関数と同じ動作をしますが、出力フォーマット文字列を最初の引数として受け入れます。
範囲ビューパイプライン(STL範囲と範囲V3の両方)を介して流れるデータを印刷するには、 IC_V関数を使用して、前のビューから受信した入力を印刷します。 IC_V関数は範囲ビューパイプライン内にあるため、各要素が生成され、印刷は怠lazに行われます。例えば:
namespace vws = std::views;
auto v0 = vws::iota( ' a ' ) | vws::enumerate | IC_V() | vws::take( 3 );
for ( auto e : v0)
{
// ...
}このコードでは、 v0が作成されたときに、それを繰り返すときに、何も印刷されません。 forループの各反復で、出力が得られるまで1つの行が印刷されます。
ic| range_view_61:53[0]: (0, 'a')
ic| range_view_61:53[1]: (1, 'b')
ic| range_view_61:53[2]: (2, 'c')
注記
IceCream-CPPは、Range-V3ライブラリがインストールされているかどうかを検出しようとしますが、もしそうなら、それへのサポートは自動的に有効になります。ただし、C ++ 11およびC ++ 14を使用する場合、システムに範囲V3がある可能性がありますが、IceCreamはそれを見つけません。 range-v3へのサポートが有効になっていることを確認するには、 icecream.hppヘッダーを含める前に、macro ICECREAM_RANGE_V3を定義するだけです
IC_V関数には、 IC_V(name, projection) 2つのオプションパラメーターがあります。
印刷時にビューに使用される変数名。印刷レイアウトは次のとおりです。 <name>[<idx>]: <value> 。名前パラメーターが使用されていない場合、 <name>のデフォルト値はrange_view_<source_location>です。
コード:
vws::iota ( ' a ' ) | vws::enumerate | IC_V( " foo " ) | vws::take( 2 );反復したときに印刷します:
ic| foo[0]: (0, 'a')
ic| foo[1]: (1, 'b')
前のビューから要素を入力し、実際のオブジェクトを返すように印刷する必要がある呼び出し可能。
コード:
vws::iota ( ' a ' ) | vws::enumerate | IC_V([]( auto e){ return std::get< 1 >(e);}) | vws::take( 2 );反復したときに印刷します:
ic| range_view_61:53[0]: 'a'
ic| range_view_61:53[1]: 'b'
注記
IC_V関数は、以前のビューから受信されたとまったく同じように、変更されていない入力要素を次のビューに転送します。 projection関数によって行われたアクションはそれに影響を与えません。
Variant IC_FV IC_V関数と同じ動作を持っていますが、出力フォーマット文字列を最初の引数として受け入れます。
正確に1つの引数で呼び出された場合を除き、 IC関数はすべての入力引数でタプルを返します。 1つの引数で呼び出されると、引数自体が返されます。
これは、 ICを使用して呼び出しポイントで関数引数を検査できるように、この方法で行われ、コード変更はそれ以上ありません。コード:
my_function (IC(MyClass{})); MyClassオブジェクトは、 IC関数が存在していないのとまったく同じmy_functionに転送されます。 my_function 、 MyClassオブジェクトへのrvalue参照を受け取り続けます。
ただし、このアプローチは、関数に多くの引数がある場合、それほど実用的ではありません。コード:
my_function (IC(a), IC(b), IC(c), IC(d)); IC関数の4倍の書き込みに加えて、印刷された出力は4つの異なる線で分割されます。次のようなもの:
ic| a: 1
ic| b: 2
ic| c: 3
ic| d: 4
残念ながら、4つの引数をすべて1回のIC呼び出しで包むだけでは機能しません。返された値は(a, b, c, d)を備えたstd:::tupleになり、 my_function 4つの引数を期待します。
それを回避するために、 IC_A関数があります。 IC_A IC関数とまったく同じように動作しますが、最初の引数として呼ばれる可能性を受け取り、次のすべての引数を使用して呼び出し、その前にすべてを印刷します。その前の例コードは、次のように書き換えることができます。
IC_A (my_function, a, b, c, d);そして今回は印刷します:
ic| a: 1, b: 2, c: 3, d: 4
IC_A関数は、コール可能なものによって返されるのと同じ値を返します。コード:
auto mc = std::make_unique<MyClass>();
auto r = IC_A(mc->my_function, a, b);まったく同じ動作:
auto mc = std::make_unique<MyClass>();
auto r = mc-> my_function (a, b);ただし、 aとbの値を印刷します。
バリアントIC_FA IC_A関数と同じ動作をしますが、呼び出し可能な引数の前でさえ、出力フォーマット文字列を最初の引数として受け入れます。
印刷中に値をフォーマットする方法を構成することができます。次のコード:
auto a = int { 42 };
auto b = int { 20 };
IC_F ( " #X " , a, b);印刷します:
ic| a: 0X2A, b: 0X14
プレーンIC機能の代わりにIC_Fバリアントを使用する場合。それぞれIC_AとIC_Vの代わりにIC_FAとIC_FVを使用する場合、同様の結果が得られます。
フォーマット関数バリアント( IC_FおよびIC_FA )を使用する場合、同じフォーマット文字列がデフォルトですべての引数に適用されます。それは、明確なフォーマットを使用して引数を持ちたい場合、または議論に相互に有効な構文を持つ複数のタイプがある場合に問題になる可能性があります。したがって、特定の引数に個別のフォーマット文字列を設定するには、 IC_関数でそれを包むことができます。コード:
auto a = int { 42 };
auto b = int { 20 };
IC_F ( " #X " , a, IC_( " d " , b));印刷します:
ic| a: 0X2A, b: 20
IC_関数は、プレーンIC (またはIC_A )関数内でも使用できます。
auto a = int { 42 };
auto b = int { 20 };
IC (IC_( " #x " , a), b);印刷します:
ic| a: 0x2a, b: 20
IC_関数呼び出しの最後の引数は、印刷されるものであり、最後に前に来る他のすべての引数は、 to_string関数を使用して文字列に変換され、結果のフォーマット文字列として連結されます。
auto a = float { 1.234 };
auto width = int { 7 };
IC (IC_( " *< " ,width, " .3 " , a));結果として、文字列"*<7.3"入力され、印刷されます。
ic| a: 1.23***
例の完全性のために、 IC_FAとIC_FVの使用は次のとおりです。
IC_FA ( " #x " , my_function, 10 , 20 );
auto rv0 = vws::iota( 0 ) | IC_FV( " [::2]:#x " , " bar " ) | vws::take( 5 );これは印刷されます:
ic| 10: 0xa, 20: 0x14
そして、 rv0で反復するとき:
ic| bar[0]: 0
ic| bar[2]: 0x2
ic| bar[4]: 0x4
IC_FおよびIC_FAに、フォーマット文字列の構文仕様は、印刷されるタイプTとICECREAMが使用するタイプの印刷戦略の両方に依存します。
IC_FVに、範囲形式の文字列と同じ場合のフォーマット構文。
C ++での文字エンコードは乱雑です。
char8_t 、 char16_t 、およびchar32_t文字列はよく定義されています。それらは能力があり、それぞれ8、16、および32ビットのユニコードコードユニットを保持しており、それぞれUTF-8、UTF-16、およびUTF-32でエンコードされています。
char文字列には、明確に定義されたコードユニットビットサイズ( CHAR_BIT 、通常は8ビットで与えられます)がありますが、エンコードに関する要件はありません。
wchar_t文字列には、明確に定義されたコードユニットサイズも、そのエンコードに関する要件もありません。
このようなコードで:
auto const str = std::string{ " foo " };
std::cout << str;関心のある3つのキャラクターエンコードポイントがあります。最初のものでは、コンパイルする前に、そのコードは、不特定の「ソースエンコーディング」のソースファイルになります。 2番目の関心点では、コンパイルされたバイナリには、「Foo」文字列が不特定の「実行エンコード」に保存されます。最後に、3番目のポイントでは、 std::coutによって受信された「Foo」バイトストリームが最終的にシステムに転送されます。これは、ストリームが不特定の「出力エンコード」でエンコードされると予想します。
キャラクターエンコーディングの3つの関心点から、「実行エンコード」と「出力エンコード」の両方が、Icecream-CPPの内部作業に影響を及ぼし、両方で使用されるエンコードが何であるかを確実に知る方法はありません。この不確実性に直面して、採用された戦略は、データを適切なエンコードに変換して、必要に応じて独自の実装を使用できるようにする合理的なデフォルトトランスコーディング関数を提供します。
幅の広い文字列とユニコードの文字列タイプ(以下で説明)を除き、他のタイプを印刷するときは、「実行エンコード」にシリアル化されたテキストデータが表示されます。 「実行エンコード」は、「出力エンコード」と同じである場合と同じである場合があります。これは、構成された出力によって予想されるエンコードです。そのため、そのデータを出力に送信する前に、「出力エンコーディング」にあることを確認するためにそれをトランスコードする必要があります。そのため、テキストデータを出力に配信する前に、構成されたoutput_transcoder関数に送信します。これにより、正しい「出力エンコード」でエンコードされるようにする必要があります。
幅の広い文字列タイプとUnicodeの文字列タイプを印刷するときは、テキストデータが予想される「実行エンコード」からエンコードされている明確な文字である可能性があるため、もう1つのトランスコーディングレベルが必要です。そのため、追加のロジックが適用されて、出力に送信する前に文字列が「実行エンコード」にあることを確認します。これについては、広い文字列とUnicode文字列セクションでさらに説明します。
IceCream-CPP構成システムは、「範囲で階層化された」動作します。基底レベルでは、グローバルIC_CONFIGオブジェクトがあります。そのグローバルなインスタンスは、グローバル変数に予想されるように、ランニングプログラム全体で共有されています。デフォルト値にすべての設定オプションを使用して作成され、変更はプログラム全体で容易に見られます。
コードの任意の時点で、新しいIC_CONFIG変数をインスタンス化して、 IC_CONFIG_SCOPE()マクロを呼び出すことにより、現在のスコープで新しい構成レイヤーを作成できます。この新しいインスタンスのすべての構成オプションは、デフォルトで「Unset」状態になり、まだ設定されていないオプション値への要求は親に委任されます。そのリクエストは、最初のオプションが回答を設定するまで、親チェーンで上昇します。
すべての構成オプションは、 IC_CONFIGオブジェクトのアクセサのメソッドを使用して設定され、それらをチェーンすることができます。
IC_CONFIG
.prefix( " ic: " )
.show_c_string( false )
.line_wrap_width( 70 ); IC_CONFIG 、衝突を非常にありそうもないという面白い名前を持つ通常の変数です。 IC*(...)マクロを呼び出すと、他の通常の変数に適用された同じルールを使用して、資格のない名前検索を行うことにより、SCOPEでIC_CONFIGインスタンスを選択します。
コードで上記のすべてを要約するには:
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
}行Aでは、 IC_CONFIGのline_wrap_width 、 context_delimiter 、およびshow_c_stringの値は次のとおりです20 、 "|" 、そしてfalse 。
最も内側のスコープブロックの閉鎖後、行Bでは、 IC_CONFIGのline_wrap_width 、 context_delimiter 、およびshow_c_stringの値は次のとおりです20 、 "|" 、そしてtrue 。
IC_CONFIGオブジェクトの読み取りおよび書き込み操作は、スレッドセーフです。
注記
グローバルインスタンス以外のIC_CONFIGの変更は、現在の範囲内でのみ見られます。結果として、これらの変更は、呼び出された関数の範囲に伝播しません。
IC(...)マクロの出力を有効または無効にし、デフォルトを有効にします。
auto is_enabled () const -> bool; auto enable () -> Config&;
auto disable () -> Config&;コード:
IC ( 1 );
IC_CONFIG.disable();
IC ( 2 );
IC_CONFIG.enable();
IC ( 3 );印刷します:
ic| 1: 1
ic| 3: 3
シリアル化されたテキストデータが印刷される場所を設定します。デフォルトでは、そのデータは、 std::cerrと同じ標準エラー出力に印刷されます。
auto output () const -> std::function<void(std::string const &)>; template < typename T>
auto output (T&& t) -> Config&;タイプTいずれかになる可能性があります:
std::ostreamから継承するクラス。push_back(char)を持つクラス。*it = 'c'たとえば、コード:
auto str = std::string{};
IC_CONFIG.output(str);
IC ( 1 , 2 ); str文字列に出力"ic| 1: 1, 2: 2n"を印刷します。
警告
Icecream-CPPは引数tの所有権を取得しないため、ユーザーが生きていることを確認するために、ユーザーが注意する必要があります。
各出力の前に印刷されるテキストを生成する関数。
auto prefix () const -> std::function<std::string()>; template < typename ... Ts>
auto prefix (Ts&& ...values) -> Config&;タイプTsは次のいずれかになります。
T() -> U 。ここで、 U operator<<(ostream&, U)を持っています。印刷されたプレフィックスは、これらすべての要素の連結になります。
コード:
IC_CONFIG.prefix( " icecream| " );
IC ( 1 );
IC_CONFIG.prefix([]{ return 42 ;}, " - " );
IC ( 2 );
IC_CONFIG.prefix( " thread " , std::this_thread::get_id, " | " );
IC ( 3 );印刷します:
icecream| 1: 1
42- 2: 2
thread 1 | 3: 3
char*変数をヌル終端Cストリング( true )またはchar ( false )へのポインターとして解釈する必要があるかどうかを制御します。デフォルト値はtrueです。
auto show_c_string () const -> bool; auto show_c_string ( bool value) -> Config&;コード:
char const * flavor = " mango " ;
IC_CONFIG.show_c_string( true );
IC (flavor);
IC_CONFIG.show_c_string( false );
IC (flavor);印刷します:
ic| flavor: "mango";
ic| flavor: 0x55587b6f5410
システム「実行エンコード」のシステム「エンコード」内のchar文字列に定義されたシステムからwchar_t文字列をトランスコードする関数。
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&;入力文字列がnullターミネーターで終了するという保証はありません(これはstring_viewの実際のセマンティックです)ため、ユーザーは入力文字列サイズの値を遵守する必要があります。
デフォルトの実装は、Cロケールが「C」または「POSIX」以外の値に設定されているかどうかを確認します。はいの場合、入力をSTD :: wcrtomb関数に転送します。それ以外の場合は、入力がUnicodeエンコード(UTF-16またはUTF-32、それに応じてwchar_tのサイズに)であると仮定し、UTF-8にトランスコードします。
UTF-32エンコーディングからシステム「実行エンコード」のchar文字列まで、 char32_t文字列をトランスコードする関数。
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&;入力文字列がnullターミネーターで終了するという保証はありません(これはstring_viewの実際のセマンティックです)ため、ユーザーは入力文字列サイズの値を遵守する必要があります。
デフォルトの実装では、Cロケールが「C」または「POSIX」以外の値に設定されていることを確認します。はいの場合、入力がSTD :: C32RTOMB関数に転送されます。それ以外の場合は、UTF-8にトランスコードされます。
この関数は、すべてのchar8_t 、 char16_t 、およびchar32_t文字列をトランスコードするために使用されます。 char8_tおよびchar16_t文字列をトランスコードすると、この関数への入力として送信される前に、最初にchar32_t文字列に変換されます。
構成された出力で予想されるように、システム「実行エンコード」システム「実行エンコード」からシステム「出力エンコード」のchar文字列に至るまでのchar文字列をトランスコードする関数。
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&;入力文字列がnullターミネーターで終了するという保証はありません(これはstring_viewの実際のセマンティックです)ため、ユーザーは入力文字列サイズの値を遵守する必要があります。
デフォルトの実装は、「実行エンコード」が「出力エンコード」と同じであると想定しており、変更されていない入力を返すだけです。
出力が複数の行で破損する前の文字の最大数。 70のデフォルト値。
auto line_wrap_width () const -> std::size_t; auto line_wrap_width (std:: size_t value) -> Config&;変数を印刷する場合でも、コンテキスト(ソース名、行番号、および関数名)を印刷する必要がある場合。デフォルト値はfalseです。
auto include_context () const -> bool; auto include_context ( bool value) -> Config&;コンテキストテキストを変数値から分離する文字列。デフォルト値は"- "です。
auto context_delimiter () const -> std::string; auto context_delimiter (std::string const & value) -> Config&;印刷できるためには、タイプT次のセクションで説明されている戦略の1つを満たす必要があります。複数の戦略が満たされていることが発生した場合、優先順位が高い戦略が選択されます。
優先順位が最も高い戦略は、STLストリームベースのI/Oを使用することです。その結果、タイプTのオブジェクトを印刷するとき、過負荷の関数operator<<(ostream&, T)が存在する場合、使用されます。
C文字列はあいまいです。 char* foo変数は、単一のcharへのポインターとして、またはヌル終端文字列として解釈する必要がありますか?同様に、 char bar[]は単一文字の配列ですか、それともヌル終端文字列ですか? char baz[3] 3つのシングル文字を持つ配列ですか、それともサイズ2と'