iCecream-CPP是一個(單個標頭)庫,可幫助C ++ 11和向前進行打印調試。
在編譯器資源管理器嘗試一下!
內容
使用冰淇淋,執行檢查:
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作為其唯一的依賴性。使用它的最直接方法是將icecream.hpp標頭複製到您的項目中。
要正確安裝IT系統寬,以及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上找到:
conan install icecream-cpp/0.3.1@如果使用cmake:
find_package (IcecreamCpp)
include_directories ( ${IcecreamCpp_INCLUDE_DIRS} )將在“包含路徑”列表中添加已安裝的目錄。
在源文件中加入icecream.hpp標頭之後:
# include < icecream.hpp > IC , IC_A和IC_V的所有功能都將獲得ICECREAM-CPP庫的所有功能;以及各自的對應物IC_F , IC_FA和IC_FV ;這種行為相同,但接受輸出格式字符串與其第一個參數。
IC是最簡單的冰淇淋函數。如果沒有參數,則將打印前綴,源文件名,當前行號和當前函數簽名。代碼:
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函數在範圍視圖管道範圍內,因此在生成每個元素時將懶惰地進行打印。例如:
namespace vws = std::views;
auto v0 = vws::iota( ' a ' ) | vws::enumerate | IC_V() | vws::take( 3 );
for ( auto e : v0)
{
// ...
}在此代碼中,當創建v0時,就在其上迭代時,什麼都不會打印。在for循環中的每次迭代中,將打印一行,直到我們有輸出為止:
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將嘗試檢測是否安裝了範圍V3庫,如果是,則將自動啟用其支持。但是,當使用C ++ 11和C ++ 14時,有可能在系統中使用範圍V3,但是冰淇淋沒有找到它。為了確保啟用了對范圍V3的支持,只需在包含icecream.hpp標頭之前定義宏ICECREAM_RANGE_V3
IC_V函數具有兩個可選參數, IC_V(name, projection) 。
打印時用於視圖的變量名稱。打印佈局為: <name>[<idx>]: <value> 。如果未使用名稱參數,則默認值為<name> is 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函數執行的任何動作都不會對此產生任何影響。
變體IC_FV具有與IC_V函數相同的行為,但接受輸出格式字符串作為其第一個參數。
除非與一個恰好的一個參數調用,否則IC函數將返回帶有其所有輸入參數的元組。如果用一個參數調用,它將返回參數本身。
這樣做是這樣這樣做的,因此您可以使用IC在呼叫點檢查函數參數,而無需更改代碼。在代碼中:
my_function (IC(MyClass{})); MyClass對象將被轉發到my_function與不存在IC函數的情況完全相同。 my_function將繼續收到對MyClass對象的RVALUE引用。
但是,當功能有很多論點時,這種方法並不那麼實用。在代碼上:
my_function (IC(a), IC(b), IC(c), IC(d));除了編寫四次IC功能外,印刷輸出還將分為四個不同的線條。像:
ic| a: 1
ic| b: 2
ic| c: 3
ic| d: 4
不幸的是,只需將所有四個參數包裝在一個IC調用中,也將行不通。返回的值將是(a, b, c, d)的std:::tuple ,而my_function則期望四個參數。
為了解決此問題,有IC_A功能。 IC_A行為與IC函數完全一樣,但是接收到其第一個參數,並將使用所有下一個參數稱呼它,然後在此之前打印所有參數。以前的示例代碼可以重寫為:
IC_A (my_function, a, b, c, d);這次它將打印:
ic| a: 1, b: 2, c: 3, d: 4
IC_A函數將返回與Callable返回的相同值。代碼:
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_F變體而不是普通IC函數時。如果分別使用IC_FA和IC_FV代替IC_A和IC_V ,將獲得類似的結果。
當使用格式函數變體( 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位的Unicode代碼單位,並且它們也分別在UTF-8,UTF-16和UTF-32中編碼。
char字符串具有明確定義的代碼單位位大小(由CHAR_BIT給出,通常為8位),但是對其編碼沒有任何要求。
wchar_t字符串既沒有定義明確的代碼單位大小,也沒有對其編碼的任何要求。
在這樣的代碼中:
auto const str = std::string{ " foo " };
std::cout << str;我們將有三個字符編碼興趣點。在第一個編譯之前,該代碼將在未指定的“源編碼”中的源文件中。在第二個興趣點中,編譯的二進製文件將具有保存在未指定的“執行編碼”中的“ foo”字符串。最後,在第三點, std::cout收到的“ FOO”字節流最終將轉發到系統,該系統期望該流正在編碼中,也未指定的“輸出編碼”。
從那個字符編碼的三個興趣點開始,“執行編碼”和“輸出編碼”都會影響iCecream-cpp的內部工作,並且無法確定他們兩個中使用的編碼是什麼。面對這種不確定性,採用的策略是提供合理的默認轉碼功能,它將嘗試將數據轉換為正確的編碼,並允許用戶在需要時使用其自己的實現。
除了寬和Unicode字符串類型(下面討論),當打印任何其他類型時,我們將在“執行編碼”中具有其序列化文本數據。 “執行編碼”可能與“輸出編碼”可能不一樣,這是由配置的輸出期望的編碼。因此,在將數據發送到輸出之前,我們必須對其進行轉編碼,以確保我們將其包含在“輸出編碼”中。為此,在將文本數據傳遞到輸出之前,我們將其發送到已配置的output_transcoder函數,該函數必須確保其在正確的“輸出編碼”中編碼。
在打印寬和Unicode字符串類型時,我們需要具有更多的轉編碼級別,因為文本數據可能是從預期的“執行編碼”中編碼的獨特字符。因此,在將它們發送到輸出之前,應用了其他邏輯以確保字符串在“執行編碼”中。在寬字符串和Unicode字符串部分中進一步討論了這一點。
iCecream-CPP配置系統“由范圍分層”起作用。在基礎級別,我們具有全局IC_CONFIG對象。整個運行程序共享該全局實例,正如全局變量所期望的那樣。它是用所有配置選項以其默認值創建的,並且整個程序都很容易看到任何更改。
在代碼的任何時候,我們都可以通過實例化新的IC_CONFIG變量來在當前範圍內創建一個新的配置層,調用IC_CONFIG_SCOPE()宏。默認情況下,此新實例的所有配置選項都將處於“不設置”狀態,並且尚未設置的任何選項值請求都將委派給其父。該請求將在父鏈上上升,直到第一個具有該選項設置答案為止。
所有配置選項都是通過使用IC_CONFIG對象的訪問器方法來設置的,並且可以鏈接它們:
IC_CONFIG
.prefix( " ic: " )
.show_c_string( false )
.line_wrap_width( 70 ); IC_CONFIG只是一個常規變量,具有有趣的名稱,使碰撞極不可能。當調用任何IC*(...)宏時,它將使用應用於任何其他常規變量的相同規則,在範圍內選擇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行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*變量解釋為null終止的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
將wchar_t字符串轉換為從系統編碼到系統“執行編碼”中的char字符串的函數。
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&;不能保證輸入字符串將在零終終端結束(這是String_view的實際語義),因此用戶必須觀察輸入字符串大小值。
默認實現將檢查C語言環境是否設置為“ C”或“ POSIX”以外的其他值。如果是,則將輸入轉發到std :: Wcrtomb函數。否則,它將假定輸入是Unicode編碼的(因此,UTF-16或UTF-32,因此,將其轉換為wchar_t的字節大小),並將其轉換為UTF-8。
將char32_t字符串轉移的函數,從編碼的UTF-32到系統“執行編碼”中的char字符串。
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&;不能保證輸入字符串將在零終終端結束(這是String_view的實際語義),因此用戶必須觀察輸入字符串大小值。
默認實現將檢查C區域設置為其他值以外的其他值。如果是,則將輸入轉發到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&;不能保證輸入字符串將在零終終端結束(這是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型必須滿足下一節中描述的一種策略。如果碰巧滿足了多種策略,將選擇具有更高優先級的策略。
優先級最高的策略是使用基於STL的I/O。因此,當打印T型對象時,如果存在重載函數operator<<(ostream&, T) ,則將使用。
c字符串模棱兩可。 char* foo變量是否應該被解釋為指向單個char的指針或無效的字符串?同樣, char bar[]變量是單個字符的數組還是null終止的字符串? char baz[3]是一個帶有三個單個字符的數組,還是尺寸二和a '