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 '