Icecream-CPP adalah perpustakaan kecil (header tunggal) untuk membantu dengan debugging cetak di C ++ 11 dan maju.
Cobalah di Compiler Explorer!
Isi
Dengan Icecream, inspeksi eksekusi:
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;
}dapat diberi kode sebagai gantinya:
auto my_function ( int i, double d) -> void
{
IC ();
if (condition)
IC ();
else
IC ();
}dan akan mencetak sesuatu seperti:
ic| test.cpp:34 in "void my_function(int, double)"
ic| test.cpp:36 in "void my_function(int, double)"
Juga, inspeksi variabel apa pun seperti:
std::cout << " a: " << a
<< " , b: " << b
<< " , sum(a, b): " << sum(a, b)
<< std::endl;dapat disederhanakan untuk:
IC (a, b, sum(a, b));dan akan mencetak:
ic| a: 7, b: 2, sum(a, b): 9
Kami juga dapat memeriksa data yang mengalir melalui pipa tampilan rentang (baik rentang STL dan range-V3), dengan memasukkan fungsi IC_V() pada titik menarik:
auto rv = std::vector< int >{ 1 , 0 , 2 , 3 , 0 , 4 , 5 }
| vws::split( 0 )
| IC_V()
| vws::enumerate; Sehingga ketika kita beralih pada rv , kita akan melihat pencetakan:
ic| range_view_63:16[0]: [1]
ic| range_view_63:16[1]: [2, 3]
ic| range_view_63:16[2]: [4, 5]
Perpustakaan ini terinspirasi oleh Perpustakaan Icecream Python asli.
Icecream-CPP adalah satu file, pustaka header saja, memiliki STL sebagai satu-satunya ketergantungannya. Cara paling langsung untuk menggunakannya adalah hanya menyalin header icecream.hpp ke dalam proyek Anda.
Untuk menginstal sistemnya dengan benar, bersama dengan file proyek CMake, jalankan perintah ini di direktori root proyek icecream-cpp:
mkdir build
cd build
cmake ..
cmake --install .Jika menggunakan NIX, icecream-cpp dapat dimasukkan sebagai input serpihan sebagai
inputs . icecream-cpp . url = "github:renatoGarcia/icecream-cpp" ; Langganan Icecream-CPP mendefinisikan overlay, sehingga dapat digunakan saat mengimpor nixpkgs :
import nixpkgs {
system = "x86_64-linux" ;
overlays = [
icecream-cpp . overlays . default
] ;
} Melakukan ini, derivasi icecream-cpp akan ditambahkan ke set atribut nixpkgs .
Contoh kerja tentang cara menggunakan icecream-cpp dalam proyek serpihan ada di sini.
Versi yang dirilis juga tersedia di Conan:
conan install icecream-cpp/0.3.1@Jika menggunakan cmake:
find_package (IcecreamCpp)
include_directories ( ${IcecreamCpp_INCLUDE_DIRS} )akan menambahkan direktori yang diinstal dalam daftar Paths Include.
Setelah memasukkan header icecream.hpp ke dalam file sumber:
# include < icecream.hpp > Semua fungsi pustaka icecream-CPP akan tersedia oleh fungsi IC , IC_A , dan IC_V ; Bersama dengan rekan masing -masing IC_F , IC_FA , dan IC_FV ; Itu berperilaku sama tetapi menerima string pemformatan output sebagai argumen pertamanya.
IC adalah yang paling sederhana dari fungsi icecream. Jika dipanggil tanpa argumen itu akan mencetak awalan, nama file sumber, nomor baris saat ini, dan tanda tangan fungsi saat ini. Kode:
auto my_function ( int foo, double bar) -> void
{
// ...
IC ();
// ...
}akan mencetak:
ic| test.cpp:34 in "void my_function(int, double)"
Jika dipanggil dengan argumen itu akan mencetak awalan, nama -nama argumen itu, dan nilainya. Kode:
auto v0 = std::vector< int >{ 1 , 2 , 3 };
auto s0 = std::string{ " bla " };
IC (v0, s0, 3.14 );akan mencetak:
ic| v0: [1, 2, 3], s0: "bla", 3.14: 3.14
Varian IC_F berperilaku sama dengan fungsi IC , tetapi menerima string pemformatan output sebagai argumen pertama.
Untuk mencetak data yang mengalir melalui pipa tampilan rentang (baik rentang STL dan range-V3), kami menggunakan fungsi IC_V , yang akan mencetak input apa pun yang diterimanya dari tampilan sebelumnya. Karena fungsi IC_V berada dalam pipa tampilan rentang, pencetakan akan dilakukan dengan malas, sementara setiap elemen dihasilkan. Misalnya:
namespace vws = std::views;
auto v0 = vws::iota( ' a ' ) | vws::enumerate | IC_V() | vws::take( 3 );
for ( auto e : v0)
{
// ...
} Dalam kode ini tidak ada yang akan dicetak saat v0 dibuat, tepat ketika mengulanginya. Pada setiap iterasi di loop for satu baris akan dicetak, sampai kita memiliki output:
ic| range_view_61:53[0]: (0, 'a')
ic| range_view_61:53[1]: (1, 'b')
ic| range_view_61:53[2]: (2, 'c')
Catatan
Icecream-CPP akan mencoba mendeteksi jika pustaka Range-V3 diinstal, dan jika demikian, dukungan untuk itu akan diaktifkan secara otomatis. Saat menggunakan C ++ 11 dan C ++ 14, ada kemungkinan memiliki Range-V3 dalam sistem, tetapi esream tidak menemukannya. Untuk memastikan bahwa dukungan ke Range-V3 diaktifkan, cukup tentukan makro ICECREAM_RANGE_V3 sebelum memasukkan header icecream.hpp
Fungsi IC_V memiliki dua parameter opsional, IC_V(name, projection) .
Nama variabel yang digunakan untuk tampilan saat mencetak. Tata letak pencetakan adalah: <name>[<idx>]: <value> . Jika parameter nama tidak digunakan, nilai default ke <name> adalah range_view_<source_location> .
Kode:
vws::iota ( ' a ' ) | vws::enumerate | IC_V( " foo " ) | vws::take( 2 );Ketika Iterasi akan mencetak:
ic| foo[0]: (0, 'a')
ic| foo[1]: (1, 'b')
Callable yang akan menerima sebagai input elemen dari tampilan sebelumnya dan harus mengembalikan objek yang sebenarnya untuk dicetak.
Kode:
vws::iota ( ' a ' ) | vws::enumerate | IC_V([]( auto e){ return std::get< 1 >(e);}) | vws::take( 2 );Ketika Iterasi akan mencetak:
ic| range_view_61:53[0]: 'a'
ic| range_view_61:53[1]: 'b'
Catatan
Fungsi IC_V masih akan meneruskan ke tampilan berikutnya elemen input yang tidak berubah, persis seperti yang diterima dari tampilan sebelumnya. Tidak ada tindakan yang dilakukan oleh fungsi projection akan berpengaruh pada itu.
Varian IC_FV memiliki perilaku yang sama dengan fungsi IC_V , tetapi menerima string pemformatan output sebagai argumen pertama.
Kecuali ketika dipanggil dengan tepat satu argumen, fungsi IC akan mengembalikan tuple dengan semua argumen inputnya. Jika dipanggil dengan satu argumen itu akan mengembalikan argumen itu sendiri.
Ini dilakukan dengan cara ini sehingga Anda dapat menggunakan IC untuk memeriksa argumen fungsi pada titik panggilan, tanpa perubahan kode lebih lanjut. Dalam kode:
my_function (IC(MyClass{})); Objek MyClass akan diteruskan ke my_function persis sama seperti jika fungsi IC tidak ada. my_function akan terus menerima referensi RValue ke objek MyClass .
Namun pendekatan ini tidak begitu praktis ketika fungsi memiliki banyak argumen. Pada kode:
my_function (IC(a), IC(b), IC(c), IC(d)); Selain menulis empat kali fungsi IC , output yang dicetak akan dibagi dalam empat baris berbeda. Sesuatu seperti:
ic| a: 1
ic| b: 2
ic| c: 3
ic| d: 4
Sayangnya, hanya membungkus keempat argumen dalam satu panggilan IC tidak akan berfungsi juga. Nilai yang dikembalikan akan menjadi std:::tuple dengan (a, b, c, d) dan my_function mengharapkan empat argumen.
Untuk mengatasi hal itu, ada fungsi IC_A . IC_A berperilaku persis seperti fungsi IC , tetapi menerima callable sebagai argumen pertama, dan akan menyebutnya menggunakan semua argumen berikutnya, mencetak semuanya sebelum itu. Kode contoh sebelumnya dapat ditulis ulang sebagai:
IC_A (my_function, a, b, c, d);Dan kali ini akan mencetak:
ic| a: 1, b: 2, c: 3, d: 4
Fungsi IC_A akan mengembalikan nilai yang sama seperti yang dikembalikan oleh yang dapat dipanggil. Kode:
auto mc = std::make_unique<MyClass>();
auto r = IC_A(mc->my_function, a, b);berperilaku persis sama dengan:
auto mc = std::make_unique<MyClass>();
auto r = mc-> my_function (a, b); tetapi akan mencetak nilai a dan b .
Varian IC_FA berperilaku sama dengan fungsi IC_A , tetapi menerima string pemformatan output sebagai argumen pertama, bahkan sebelum argumen yang dapat dipanggil.
Dimungkinkan untuk mengonfigurasi bagaimana nilainya harus diformat saat mencetak. Kode berikut:
auto a = int { 42 };
auto b = int { 20 };
IC_F ( " #X " , a, b);akan mencetak:
ic| a: 0X2A, b: 0X14
Saat menggunakan varian IC_F alih -alih fungsi IC polos. Hasil yang serupa akan diperoleh jika menggunakan IC_FA dan IC_FV masing -masing sebagai IC_A dan IC_V .
Saat menggunakan varian fungsi pemformatan ( IC_F dan IC_FA ), string pemformatan yang sama akan diterapkan secara default untuk semua argumen. Itu bisa menjadi masalah jika kita ingin memiliki argumen dengan pemformatan yang berbeda, atau jika argumen memiliki banyak jenis dengan sintaksis yang tidak saling valid. Oleh karena itu, untuk mengatur string pemformatan yang berbeda ke argumen tertentu kita dapat membungkusnya dengan fungsi IC_ . Kode:
auto a = int { 42 };
auto b = int { 20 };
IC_F ( " #X " , a, IC_( " d " , b));akan mencetak:
ic| a: 0X2A, b: 20
Fungsi IC_ dapat digunakan dalam fungsi IC (atau IC_A ) biasa juga:
auto a = int { 42 };
auto b = int { 20 };
IC (IC_( " #x " , a), b);akan mencetak:
ic| a: 0x2a, b: 20
Argumen terakhir dalam panggilan fungsi IC_ adalah yang akan dicetak, semua argumen lain yang datang sebelum yang terakhir akan dikonversi ke string menggunakan fungsi to_string dan digabungkan sebagai string pemformatan yang dihasilkan.
auto a = float { 1.234 };
auto width = int { 7 };
IC (IC_( " *< " ,width, " .3 " , a)); Akan memiliki hasil string pemformatan "*<7.3" , dan akan mencetak:
ic| a: 1.23***
Hanya untuk kelengkapan dalam contoh, penggunaan IC_FA dan IC_FV adalah:
IC_FA ( " #x " , my_function, 10 , 20 );
auto rv0 = vws::iota( 0 ) | IC_FV( " [::2]:#x " , " bar " ) | vws::take( 5 );Ini akan mencetak:
ic| 10: 0xa, 20: 0x14
dan saat berulang di rv0 :
ic| bar[0]: 0
ic| bar[2]: 0x2
ic| bar[4]: 0x4
Untuk IC_F dan IC_FA , spesifikasi sintaks dari string pemformatan tergantung pada tipe T yang sedang dicetak, dan dalam strategi pencetakan tipe yang digunakan oleh iCecream.
Ke IC_FV , sintaks pemformatan jika sama dengan string format rentang.
Pengkodean karakter dalam C ++ berantakan.
String char8_t , char16_t , dan char32_t didefinisikan dengan baik. Mereka mampu, dan memiliki unit kode unicode masing-masing 8, 16, dan 32 bit, dan mereka dikodekan dalam UTF-8, UTF-16, dan UTF-32 juga masing-masing.
String char memiliki ukuran bit unit kode yang ditentukan dengan baik (diberikan oleh CHAR_BIT , biasanya 8 bit), tetapi tidak ada persyaratan tentang pengkodeannya.
String wchar_t tidak memiliki ukuran unit kode yang terdefinisi dengan baik, atau persyaratan apa pun tentang pengkodeannya.
Dalam kode seperti ini:
auto const str = std::string{ " foo " };
std::cout << str; Kami akan memiliki tiga tempat pengkodean karakter yang menarik. Di yang pertama, sebelum menyusun, kode itu akan berada dalam file sumber dalam "penyandian sumber" yang tidak ditentukan. Pada titik bunga kedua, biner yang dikompilasi akan memiliki string "foo" yang disimpan dalam "pengkodean eksekusi" yang tidak ditentukan. Akhirnya pada titik ketiga, aliran byte "Foo" yang diterima oleh std::cout akhirnya akan diteruskan ke sistem, yang mengharapkan aliran yang dikodekan dalam "output encoding" yang juga tidak ditentukan.
Dari tiga titik minat pengkodean karakter, baik "pengkodean eksekusi", dan "pengkodean output" berdampak dalam kerja dalam icecream-cpp, dan tidak ada cara untuk mengetahui dengan pasti apa pengkodean yang digunakan pada keduanya. Dalam menghadapi ketidakpastian ini, strategi yang diadopsi menawarkan fungsi transcoding default yang wajar, yang akan mencoba mengonversi data ke pengkodean yang tepat, dan memungkinkan pengguna untuk menggunakan implementasinya sendiri saat dibutuhkan.
Kecuali untuk tipe string lebar dan unicode (dibahas di bawah), saat mencetak jenis lain, kami akan memiliki data tekstual serial dalam "Eksekusi Pengkodean". Bahwa "pengkodean eksekusi" mungkin atau mungkin tidak sama dengan "output encoding", yang satu ini adalah pengkodean yang diharapkan oleh output yang dikonfigurasi. Karena itu, sebelum kita mengirim data itu ke output, kita harus mentranskodenya untuk memastikan bahwa kita memilikinya di "output encoding". Untuk tujuan itu, sebelum mengirimkan data teks ke output, kami mengirimkannya ke fungsi output_transcoder yang dikonfigurasi, yang harus memastikan itu dikodekan dalam "pengkodean output" yang benar.
Saat mencetak jenis string yang luas dan unicode, kita perlu memiliki satu tingkat transkode lagi, karena ada kemungkinan bahwa data teks berada dalam pengkodean karakter yang berbeda dari "pengkodean eksekusi" yang diharapkan. Karena itu, logika tambahan diterapkan untuk memastikan bahwa string berada di "Eksekusi Pengkodean" sebelum kami mengirimkannya ke output. Ini lebih lanjut dibahas dalam string luas, dan bagian unicode strings.
Sistem konfigurasi Icecream-CPP berfungsi "berlapis dengan ruang lingkup". Pada tingkat basis kami memiliki objek IC_CONFIG global. Contoh global itu dibagikan oleh seluruh program berjalan, seperti yang diharapkan dari variabel global. Ini dibuat dengan semua opsi konfigurasi pada nilai defaultnya, dan setiap perubahan mudah dilihat oleh seluruh program.
Di setiap titik kode, kami dapat membuat lapisan konfigurasi baru pada ruang lingkup saat ini dengan instantiasi variabel IC_CONFIG baru, memanggil makro IC_CONFIG_SCOPE() . Semua opsi konfigurasi dari instance baru ini akan berada dalam status "tidak disetel" secara default, dan permintaan apa pun ke nilai opsi yang belum ditetapkan akan didelegasikan ke induknya. Permintaan itu akan naik pada rantai induk sampai yang pertama memiliki opsi yang menetapkan jawaban.
Semua opsi konfigurasi diatur dengan menggunakan metode aksesor objek IC_CONFIG , dan mereka dapat dirantai:
IC_CONFIG
.prefix( " ic: " )
.show_c_string( false )
.line_wrap_width( 70 ); IC_CONFIG hanyalah variabel reguler dengan nama lucu untuk membuat tabrakan sangat tidak mungkin. Saat memanggil IC*(...) makro, itu akan memilih instance IC_CONFIG di ruang lingkup dengan melakukan pencarian nama yang tidak memenuhi syarat, menggunakan aturan yang sama yang diterapkan pada variabel reguler lainnya.
Untuk meringkas semua hal di atas, dalam kode:
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
} Pada baris A , nilai IC_CONFIG line_wrap_width , context_delimiter , dan show_c_string akan masing -masing: 20 , "|" , dan false .
Setelah penutupan blok lingkup terdalam, pada baris B , nilai IC_CONFIG 's line_wrap_width , context_delimiter , dan show_c_string akan masing -masing: 20 , "|" , dan true .
Operasi bacaan dan penulisan pada objek IC_CONFIG adalah utas aman.
Catatan
Setiap modifikasi dalam IC_CONFIG , selain dari instance global, hanya akan dilihat dalam ruang lingkup saat ini. Sebagai konsekuensinya, modifikasi tersebut tidak akan menyebar ke ruang lingkup fungsi yang disebut.
Aktifkan atau nonaktifkan output IC(...) makro, diaktifkan default.
auto is_enabled () const -> bool; auto enable () -> Config&;
auto disable () -> Config&;Kode:
IC ( 1 );
IC_CONFIG.disable();
IC ( 2 );
IC_CONFIG.enable();
IC ( 3 );akan mencetak:
ic| 1: 1
ic| 3: 3
Set di mana data tekstual serial akan dicetak. Secara default data akan dicetak pada output kesalahan standar, sama seperti std::cerr .
auto output () const -> std::function<void(std::string const &)>; template < typename T>
auto output (T&& t) -> Config&; Dimana tipe T bisa saja:
std::ostream .push_back(char) .*it = 'c'Misalnya, kode:
auto str = std::string{};
IC_CONFIG.output(str);
IC ( 1 , 2 ); Akan mencetak output "ic| 1: 1, 2: 2n" pada string str .
Peringatan
Icecream-CPP tidak akan mengambil kepemilikan t , sehingga harus diperhatikan oleh pengguna untuk memastikan bahwa itu masih hidup.
Fungsi yang menghasilkan teks yang akan dicetak sebelum setiap output.
auto prefix () const -> std::function<std::string()>; template < typename ... Ts>
auto prefix (Ts&& ...values) -> Config&; Di mana tipe Ts bisa saja:
T() -> U , di mana U memiliki kelebihan operator<<(ostream&, U) .Awalan yang dicetak akan menjadi gabungan dari semua elemen itu.
Kode:
IC_CONFIG.prefix( " icecream| " );
IC ( 1 );
IC_CONFIG.prefix([]{ return 42 ;}, " - " );
IC ( 2 );
IC_CONFIG.prefix( " thread " , std::this_thread::get_id, " | " );
IC ( 3 );akan mencetak:
icecream| 1: 1
42- 2: 2
thread 1 | 3: 3
Kontrol Jika variabel char* harus ditafsirkan sebagai string C yang diakhiri nol ( true ) atau pointer ke char ( false ). Nilai standarnya true .
auto show_c_string () const -> bool; auto show_c_string ( bool value) -> Config&;Kode:
char const * flavor = " mango " ;
IC_CONFIG.show_c_string( true );
IC (flavor);
IC_CONFIG.show_c_string( false );
IC (flavor);akan mencetak:
ic| flavor: "mango";
ic| flavor: 0x55587b6f5410
Fungsi yang mentranskodasi string wchar_t , dari pengkodean yang ditentukan sistem ke string char dalam sistem "Eksekusi Pengkodean".
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&;Tidak ada jaminan bahwa string input akan berakhir pada terminator nol (ini adalah semantik aktual dari string_view), sehingga pengguna harus mengamati nilai ukuran string input.
Implementasi default akan memeriksa apakah lokasi C diatur ke nilai lain selain "C" atau "POSIX". Jika ya, itu akan meneruskan input ke fungsi std :: wcrtomb. Kalau tidak, ia akan mengasumsikan bahwa input adalah Unicode yang dikodekan (UTF-16 atau UTF-32, sesuai dengan ukuran byte wchar_t ), dan mentranskodinya ke UTF-8.
Fungsi yang mentranskodasi string char32_t , dari pengkodean UTF-32 ke string char dalam sistem "Eksekusi Pengkodean".
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&;Tidak ada jaminan bahwa string input akan berakhir pada terminator nol (ini adalah semantik aktual dari string_view), sehingga pengguna harus mengamati nilai ukuran string input.
Implementasi default akan memeriksa lokasi C diatur ke nilai lain selain "C" atau "POSIX". Jika ya, itu akan meneruskan input ke fungsi std :: c32rtomb. Kalau tidak, itu hanya akan memindahkannya ke UTF-8.
Fungsi ini akan digunakan untuk mentranskode semua string char8_t , char16_t , dan char32_t . Saat transcoding string char8_t dan char16_t , mereka akan dikonversi terlebih dahulu menjadi string char32_t , sebelum dikirim sebagai input ke fungsi ini.
Fungsi yang mentranskode string char , dari sistem "pengkodean eksekusi" ke string char dalam sistem "output encoding", seperti yang diharapkan oleh output yang dikonfigurasi.
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&;Tidak ada jaminan bahwa string input akan berakhir pada terminator nol (ini adalah semantik aktual dari string_view), sehingga pengguna harus mengamati nilai ukuran string input.
Implementasi default mengasumsikan bahwa "pengkodean eksekusi" sama dengan "pengkodean output", dan hanya akan mengembalikan input yang tidak berubah.
Jumlah maksimum karakter sebelum output dipecah pada beberapa baris. Nilai default 70 .
auto line_wrap_width () const -> std::size_t; auto line_wrap_width (std:: size_t value) -> Config&; Jika konteks (nama sumber, nomor baris, dan nama fungsi) harus dicetak bahkan saat mencetak variabel. Nilai default false .
auto include_context () const -> bool; auto include_context ( bool value) -> Config&; String yang memisahkan teks konteks dari nilai variabel. Nilai default adalah "- " .
auto context_delimiter () const -> std::string; auto context_delimiter (std::string const & value) -> Config&; Agar dapat dicetak, tipe T harus memenuhi salah satu strategi yang dijelaskan di bagian berikutnya. Jika terjadi bahwa banyak strategi terpenuhi, yang dengan prioritas lebih tinggi akan dipilih.
Strategi dengan prioritas tertinggi adalah menggunakan I/O berbasis STL Stream. Akibatnya, saat mencetak objek tipe T , jika ada operator<<(ostream&, T) , itu akan digunakan.
String C ambigu. Haruskah variabel char* foo ditafsirkan sebagai pointer ke satu char atau sebagai string yang diakhiri nol? Demikian juga, apakah variabel char bar[] merupakan array karakter tunggal atau string yang diakhiri nol? Apakah char baz[3] Array dengan tiga karakter tunggal atau apakah itu string ukuran dua ditambah '