dbg_macro逐行解析
0. dbg_macro的介绍
调试器很棒。但是有时候您没有时间或者耐心来正确地设置所有的东西,而只是想要一种在运行时检查某些值的快速方法。这个项目提供了一个带有 dbg (...)
宏的头文件,可以在通常编写printf (“ ...”,...)
或std::cout<< ...
的所有情况下使用。但是还有一些额外的东西。
项目地址:sharkdp/dbg-macro: A dbg(…) macro for C++ (github.com)
0.1 使用范例
#include <dbg.h>
#include <cstdint>
#include <vector>
// You can use "dbg(..)" in expressions:
int32_t factorial(int32_t n) {
if (dbg(n <= 1)) {
return dbg(1);
} else {
return dbg(n * factorial(n - 1));
}
}
int32_t main() {
std::string message = "hello";
dbg(message); // [example.cpp:15 (main)] message = "hello" (std::string)
const int32_t a = 2;
const int32_t b = dbg(3 * a) + 1; // [example.cpp:18 (main)] 3 * a = 6 (int32_t)
std::vector<int32_t> numbers{b, 13, 42};
dbg(numbers); // [example.cpp:21 (main)] numbers = {7, 13, 42} (std::vector<int32_t>)
dbg("this line is executed"); // [example.cpp:23 (main)] this line is executed
factorial(4);
return 0;
}
上面的代码产生了这个输出:
0.2 特点
- 易于阅读的彩色输出(当输出不是交互式终端时,颜色自动禁用)
- 打印文件名、行号、函数名称和原始表达式
- 为打印出的值添加类型信息
- 容器、指针、字符串字面、枚举、std::optional等的专门pretty-printers。
- 可以在表达式中使用(通过原始值传递)
- dbg.h 标头在包含时发出编译器警告(因此您不要忘记删除它)。
- 与 C++11、C++14 和 C++17 兼容并经过测试。
0.3 高级特性
- 多参数
你可以传递多个参数到dbg(…)
宏。dbg(x, y, z)
的输出和dbg(x); dbg(y); dbg(z);
是相同的:
dbg(42, "hello world", false);
Note 你必须用圆括号括起“无保护逗号”:
dbg("a vector:", (std::vector<int>{2, 3, 4}));
- 支持16进制、8进制、2进制、10进制格式
如果您想用十六进制、八进制或二进制表示来格式化整数,您可以简单地将它们包装在 dbg::hex(…)
, dbg::oct(…)
or dbg::bin(…)
:
const uint32_t secret = 12648430;
dbg(dbg::hex(secret));
- 打印类型名称
dbg(…)
已经打印了括号中每个值的类型(见上面的截图)。但有时你只是想打印一个类型(也许是因为你没有该类型的值)。在这种情况下,你可以使用 dbg::type<T>()
辅助工具来对一个给定的类型进行pretty-print。
比如说。
template <typename T>
void my_function_template() {
using MyDependentType = typename std::remove_reference<T>::type&&;
dbg(dbg::type<MyDependentType>());
}
- 打印当前时间
你可以利用 dbg::time()
来打印当前的时间戳:
dbg(dbg::time());
- 自定义
如果你想让 dbg(…)
对自定义数据类型也正常工作, 你只需要为std::ostream&
重载operator<<
:
std::ostream& operator<<(std::ostream& out, const user_defined_type& v) {
out << "…";
return out;
}
如果你想修改dbg(…)
打印的类型名, 你需要重载get_type_name
:
// Customization point for type information
namespace dbg {
std::string get_type_name(type_tag<bool>) {
return "truth value";
}
}
1、前置知识
1.1 c/c++预定义宏
c/c++为了方便程序员了解代码信息,内置了一些常用的宏
- __DATE__ 进行预处理的日期("Mmm dd yyyy"形式的字符串文字,如May 27 2006)
- __FILE__ 代表当前源代码文件名的字符串文字 ,包含了详细路径,如G:/program/study/c+/test1.c
- __LINE__ 代表当前源代码行号的整数文字,如 10
- __TIME__ 进行预处理的时间("hh:mm:ss"形式的字符串文字,如21:05:19)
- __func__ 代表当前函数名的字符串文字,如main, 该宏在c99标准中才有
__PRETTY_FUNCTION__
代表当前函数名的字符串文字,如main, 该宏在c++标准中才有
其中 __PRETTY_FUNCTION__
我们写一段测试程序,看一看它的具体功能,其它宏的用法请自行查找相关文档。
#include<iostream>
#include<vector>
#include<string>
#include<unordered_map>
#include<list>
#include<tuple>
#include<map>
using namespace std;
class student{
public:
int id;
string name;
};
void func1(void){
cout<<__PRETTY_FUNCTION__<<endl;
}
void func2(const string&){
cout<<__PRETTY_FUNCTION__<<endl;
}
int func3(vector<student>){
cout<<__PRETTY_FUNCTION__<<endl;
return {};
}
student func4(unordered_map<char, list<int>>){
cout<<__PRETTY_FUNCTION__<<endl;
return {};
}
string& func5(tuple<string, student, int>&&){
cout<<__PRETTY_FUNCTION__<<endl;
static string str;
return str;
}
bool func6(const list<student>){
cout<<__PRETTY_FUNCTION__<<endl;
return {};
}
namespace wmh{
template<class T>
void func7(){
cout<<__PRETTY_FUNCTION__<<endl;
}
}
int main(){
cout<<__PRETTY_FUNCTION__<<endl;
func1();
func2("hello");
func3(vector<student>{});
func4(unordered_map<char, list<int>>{});
func5(make_tuple(string{"11111"},student{}, 0));
list<student> lst;
func6(lst);
wmh::func7<int>();
wmh::func7<student>();
wmh::func7<const string&>();
return 0;
}
上述代码在Ubuntu下编译运行,输出结果如下:
int main()
void func1()
void func2(const string&)
int func3(std::vector<student>)
student func4(std::unordered_map<char, std::__cxx11::list<int> >)
std::string& func5(std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, student, int>&&)
bool func6(std::__cxx11::list<student>)
void wmh::func7() [with T = int]
void wmh::func7() [with T = student]
void wmh::func7() [with T = const std::__cxx11::basic_string<char>&]
可以看到 __PRETTY_FUNCTION__
这个宏异常强大,可以获得命名空间信息,函数名,函数参数的类型,函数返回值的类型,如果函数是模板,还能获得模板参数的类型。dbg_macro就是利用这个宏来实现获取变量类型的。
1.2 模板编程知识
dbg_macro这个库的实现使用到了很多模板编程知识,下面就用到的知识简要介绍,更多的内容请自行查找相关资料。
1.2.1 type_traits(相关资料)
C++提供元编程设施,诸如类型特性、编译时有理数算术,以及编译时整数序列。
- rank 获取数组类型的维数
- extent 获取数组类型在指定维度的大小
- remove_extent 从给定数组类型移除一个维度
- remove_all_extents 移除给定数组类型的所有维度
- is_same 检查两个类型是否相同
- is_volatile 检查类型是否为 volatile 限定
- is_pointer 检查类型是否为指针类型
- is_const 检查类型是否为 const 限定
- remove_volatile 从给定类型移除 volatile 限定符
- remove_const 从给定类型移除 const 限定符
- remove_pointer 移除给定类型的一层指针
- remove_reference 从给定类型移除引用
1.2.2 SFINAE机制
“替换失败不是错误” (Substitution Failure Is Not An Error)
在函数模板的重载决议中会应用此规则:当模板形参在替换成显式指定的类型或推导出的类型失败时,从重载集中丢弃这个特化,而非导致编译失败.
此特性被用于模板元编程。下面给一个例子(引用自博客c+±我们如何void_t用于SFINAE? - 糯米PHP (nuomiphp.com))。
template <typename...>
using void_t = void;
template< class , class = void >
struct has_member : std::false_type
{ };
// specialized as has_member< T , void > or discarded (SFINAE)
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : std::true_type
{ };
//上述类模板的使用
class A {
public:
int member;
};
class B {
};
static_assert( has_member< A >::value , "A" );
static_assert( has_member< B >::value , "B" );
1. has_member< A >
-
has_member< A , void_t< decltype( A::member ) > >
A::member
存在decltype( A::member )
格式良好void_t<>
有效且计算结果为void
-
has_member< A , void >
因此它选择专门的模板 -
has_member< T , void >
并评估为true_type
2. has_member< B >
-
has_member< B , void_t< decltype( B::member ) > >
B::member
不存在decltype( B::member )
格式不正确,无声地失败(sfinae)has_member< B , expression-sfinae >
所以这个模板被丢弃了
-
编译器查找以 void 作为默认参数
has_member< B , class = void >
-
has_member< B >
评估结果为false_type
1.2.3 enable_if
enable_if是c++标准提供的一个类模板,一种可能的实现如下所示:
template<bool B, class T = void>
struct enable_if {}; //version 0
template<class T>
struct enable_if<true, T> { typedef T type; }; //version 1
enable_if的作用是依据条件启用或禁用函数模板,例如:
template <typename T> //参考章节1.2.1和1.2.3
typename std::enable_if<std::is_integral<T>::value, std::string>::type
get_type_name(type_tag<T>){
//...
}
当T
为整形类型时,std::is_integral<T>::value
为True,enable_if的第二个版本更加符合需求,该版本的类模板内部有type这个符号,则上述代码编译为:
std::string get_type_name(type_tag<std::string>){
//...
}
编译通过。
当T
不为整形类型时,std::is_integral<T>::value
为False,enable_if的第一个版本更加符合需求,该版本的类模板内部没有type这个符号,则typename std::enable_if<std::is_integral<T>::value, std::string>::type
编译失败,相当于禁止掉非整型类型传入该函数模板。
1.2.4 折叠表达式
语法
语法 | 类型 | 序号 |
---|---|---|
( 形参包 运算符 … ) | 一元右折叠 | (1) |
( … 运算符 形参包 ) | 一元左折叠 | (2) |
(形参包 运算符 … 运算符 初值 ) | 二元右折叠 | (3) |
(初值 运算符 … 运算符 形参包 ) | 二元左折叠 | (4) |
- 运算符 — 下列32个二元运算符之一:
+
-
*
/
%
^
&
|
=
<
>
<<
>>
+=
-=
*=
/=
%=
^=
&=
|=
<<=
>>=
==
!=
<=
>=
&&
||
,
.*
->*
。在二元折叠中,两个运算符必须相同。 - 形参包 — 含有未展开的形参包且在顶层不含优先级低于转型(正式而言,是 转型表达式)的运算符的表达式
- 初值 — 不含未展开的形参包且在顶层不含优先级低于转型(正式而言,是 转型表达式)的运算符的表达式
注意开闭括号也是折叠表达式的一部分。
解释
折叠表达式的实例化按以下方式展开成表达式 e:
-
一元右折叠
(E 运算符 ...)
成为(E1 运算符 (... 运算符 (EN-1 运算符 EN)))
-
一元左折叠
(... 运算符 E)
成为(((E1 运算符 E2) 运算符 ...) 运算符 EN)
-
二元右折叠
(E 运算符 ... 运算符 I)
成为(E1 运算符 (... 运算符 (EN−1 运算符 (EN 运算符 I))))
-
二元左折叠
(I 运算符 ... 运算符 E)
成为((((I 运算符 E1) 运算符 E2) 运算符 ...) 运算符 EN)
(其中 N
是包展开中的元素数量)
例如,
template <typename... T>
std::string type_list_to_string() {
std::string result;
auto unused = {(result += type_name<T>() + ", ", 0)..., 0};//type_name用于获取T的类型名称字符串,将在后续章节讲解。
}
// 上面的折叠表达式属于二元右折叠,运算符为“,”逗号运算符,
// type_list_to_string<int, double, char*>()中的折叠表达式将展开为:
//{(result+=(type_name<int>()+ ", ") , 0), (result+=(type_name<double>()+ ", ") , 0), (result+=(type_name<char *>()+ ", ") , 0), 0}
将一元折叠用于长度为零的包展开时,只能使用下列运算符:
-
逻辑与(&&)。空包的值是 true
-
逻辑或(||)。空包的值是 false
-
逗号运算符(,)。空包的值是 void()
1.2.5 限定用户对函数模板的使用
template <int&... ExplicitArgumentBarrier, typename T>
void AcceptSomeReference (const T&);
<int&… ExplicitArgumentBarrier>这是一个变长模板参数(variadic template parameter),它可以接受任意数量的int&类型的参数。它的目的是防止用户显式指定模板参数,从而禁用模板参数推导(template argument deduction)。例如,如果你有一个函数模板:你不能这样调用它:
AcceptSomeReference<char> ('a');
因为编译器会认为char是ExplicitArgumentBarrier的一个参数,而不是T的参数。你只能这样调用它:
AcceptSomeReference ('a');
这样编译器才能根据’a’的类型推导出T是char。这种技巧可以让函数模板的作者保留对模板参数的控制,而不用担心用户会传入错误或多余的参数。
1.3 cout
的格式化输出
操作符 | 说明 |
---|---|
setw | 更改下个输入/输出域的宽度 |
setfill | 更改填充字符 |
dec/hex/oct | 更改用于整数输入/输出的基数 |
uppercase/nouppercase | 控制一些输出操作是否使用大写字母 |
boolalpha/noboolalpha | 在布尔值的文本和数值表示间切换 |
put_time | 按照指定格式格式化并输出日期/时间值 |
1.4 ANSI控制码
ANSI是一种字符编码,其扩展了ASCII编码。如果想在终端输出带有颜色的字体,或者控制光标的行为,可以通过ANSI来实现。ANSI控制码均以Esc
作为控制码的开始标志,其中,Esc
的ansi十进制码为 27,八进制码为33,所以可以使用 \033
表示。如果想要了解更多可以查看以下资料:
基于ANSI转义序列来构建命令行工具 – 小居 (liunian.info)
ANSI转义序列 - 维基百科,自由的百科全书 (wikipedia.org)
Ubuntu Manpage: console_codes - Linux console escape and control sequences
ANSI码 | 说明 |
---|---|
\xb[0m | 关闭所有属性 |
\x1b[1m | 设置高亮度 |
\x1b[4m | 下划线 |
\x1b[5m | 闪烁 |
\x1b[7m | 反显 |
\x1b[8m | 消隐 |
\x1b[30m – \33[37m | 设置前景色 |
\x1b[40m – \33[47m | 设置背景色 |
\x1b[nA | 光标上移n行 |
\x1b[nB | 光标下移n行 |
\x1b[nC | 光标右移n行 |
\x1b[nD | 光标左移n行 |
\x1b[y;xH | 设置光标位置(现已很少使用) |
\x1b[2J | 清屏 |
\x1b[K | 清除从光标到行尾的内容 |
\x1b[s | 保存光标位置 |
\x1b[u | 恢复光标位置 |
\x1b[?25l | 隐藏光标 |
\x1b[?25h | 显示光标 |
2、dbg.h
的解析
2.1 dbg.h中变量类型的获取
note: 为了方便讨论,我删除了很多与平台判断相关的宏判断分支,这里只讨论Linux平台相关的内容,其他平台类似
2.1.1 底层原理
c++标准提供了一些工具用于获得变量的类型信息,例如typeid, 但是标准库并没有对**typeid(expression).name()**的输出做规范,因此其输出在不同编译器的实现中都不一样,而且一般会经过特殊编码,因此无法通过这种方法获得类型信息。dbg_macro库的作者非常聪明,它巧妙地运用了c++提供的宏__PRETTY_FUNCTION__,具体实现如下方代码所示。
namespace pretty_function {
// Compiler-agnostic version of __PRETTY_FUNCTION__ and constants to
// extract the template argument in `type_name_impl`
#define DBG_MACRO_PRETTY_FUNCTION __PRETTY_FUNCTION__
static constexpr size_t PREFIX_LENGTH = //公共前缀长度
sizeof("const char* dbg::type_name_impl() [with T = ") - 1;
static constexpr size_t SUFFIX_LENGTH = sizeof("]") - 1; //后缀长度
} // namespace pretty_function
template <typename T>
const char* type_name_impl() {
return DBG_MACRO_PRETTY_FUNCTION;
}
template <typename T>
struct type_tag {}; //空类,不占用空间,只用于传递类型信息
template <int&... ExplicitArgumentBarrier, typename T> //参考章节1.2.1
typename std::enable_if<(std::rank<T>::value == 0), std::string>::type//当T为非数组类型的时候可以使用该函数
get_type_name(type_tag<T>) {
namespace pf = pretty_function;//包含前后缀长度信息
std::string type = type_name_impl<T>(); //依靠__PRETTY_FUNCTION__获取类型信息
return type.substr(pf::PREFIX_LENGTH,
type.size() - pf::PREFIX_LENGTH - pf::SUFFIX_LENGTH); //减去前后缀得到类型名称
}
在使用type_name_impl的时候,指定模板参数类型,根据1.1章节的内容,该函数就会返回“const char * dbg::type_name_impl()[with T = 具体传入的类型]”
的字符串,然后删去固定的前后缀,就可以得到变量的类型。
2.1.2 处理类型限定信息(&,&&,const,volatile)
当类型包含引用,cv,指针等限定信息时为了规范地输出类型信息,编写**type_name()对get_type_name()**进行封装。
template <typename T>
std::string type_name() {
if (std::is_volatile<T>::value) {//如果是volatile
if (std::is_pointer<T>::value) {//如果是指针,则将‘volatile’加在类型后面
return type_name<typename std::remove_volatile<T>::type>() + " volatile";
} else { //否则加在前面
return "volatile " + type_name<typename std::remove_volatile<T>::type>();
}
}
if (std::is_const<T>::value) {//如果是const,与volatile的处理类似
if (std::is_pointer<T>::value) {
return type_name<typename std::remove_const<T>::type>() + " const";
} else {
return "const " + type_name<typename std::remove_const<T>::type>();
}
}
if (std::is_pointer<T>::value) {//规范化指针类型输出
return type_name<typename std::remove_pointer<T>::type>() + "*";
}
if (std::is_lvalue_reference<T>::value) {//规范化左值引用类型输出
return type_name<typename std::remove_reference<T>::type>() + "&";
}
if (std::is_rvalue_reference<T>::value) {//规范化右值引用类型输出
return type_name<typename std::remove_reference<T>::type>() + "&&";
}
return get_type_name(type_tag<T>{});//没有限定符,直接返回
}
递归模板,get_type_name()只用于获取没有限定符的类型的名称(相当于递归的结束条件),type_name()将传入的类型以递归的形式层层释放限定符,并层层拼接,逐步生成类型的名称。下图是一个具体的例子来理解这个函数模板的工作原理:
2.1.3 优化整形的类型名称获取
优先使用带比特位数的整形类型。利用模板特化来实现。
// Prefer bitsize variant over standard integral types
#define DBG_MACRO_REGISTER_TYPE_ASSOC(t_std, t_bit) \
inline constexpr const char* get_type_name(type_tag<t_std>) { \
return std::is_same<t_std, t_bit>::value ? #t_bit : #t_std; \
}
DBG_MACRO_REGISTER_TYPE_ASSOC(unsigned char, uint8_t)
DBG_MACRO_REGISTER_TYPE_ASSOC(unsigned short, uint16_t)
DBG_MACRO_REGISTER_TYPE_ASSOC(unsigned int, uint32_t)
DBG_MACRO_REGISTER_TYPE_ASSOC(unsigned long, uint64_t)
DBG_MACRO_REGISTER_TYPE_ASSOC(signed char, int8_t)
DBG_MACRO_REGISTER_TYPE_ASSOC(short, int16_t)
DBG_MACRO_REGISTER_TYPE_ASSOC(int, int32_t)
DBG_MACRO_REGISTER_TYPE_ASSOC(long, int64_t)
例如**DBG_MACRO_REGISTER_TYPE_ASSOC(unsigned char, uint8_t)**宏展开后将变为:
inline constexpr const char* get_type_name(type_tag<unsigned char>) {
return std::is_same<unsigned char, uint8_t>::value ? "uint8_t" : "unsigned char";
}
2.1.4 c++标准容器类型的类型名称获取
从1.1章节中的例子可以看出,走某些情况下__PRETTY_FUNCTION__
给出来的c++容器类型有时会比较复杂,比如const string&
的类型名称为const std::__cxx11::basic_string<char>&
,与意料中的差距较大,因此库作者对get_type_name
进行了很多偏特化,以优化这些容器类型名称的获取。
inline std::string get_type_name(type_tag<std::string>) {
return "std::string";
}
template <typename T, size_t N>
std::string get_type_name(type_tag<std::array<T, N>>) {
return "std::array<" + type_name<T>() + ", " + std::to_string(N) + ">";
}
template <typename T>
std::string get_type_name(type_tag<std::vector<T, std::allocator<T>>>) {
return "std::vector<" + type_name<T>() + ">";
}
template <typename T1, typename T2>
std::string get_type_name(type_tag<std::pair<T1, T2>>) {
return "std::pair<" + type_name<T1>() + ", " + type_name<T2>() + ">";
}
2.1.5 数组类型的类型名称获取
template <typename T>
typename std::enable_if<(std::rank<T>::value == 1), std::string>::type
get_array_dim() {
return "[" + std::to_string(std::extent<T>::value) + "]";
}
template <typename T>
typename std::enable_if<(std::rank<T>::value > 1), std::string>::type
get_array_dim() {
return "[" + std::to_string(std::extent<T>::value) + "]" +
get_array_dim<typename std::remove_extent<T>::type>();
}
template <typename T>
typename std::enable_if<(std::rank<T>::value > 0), std::string>::type//如果是数组类型,就选用该特化版本
get_type_name(type_tag<T>) {
return type_name<typename std::remove_all_extents<T>::type>() + get_array_dim<T>();
}
数组类型的类型名称的获取,通过type_name<typename std::remove_all_extents<T>::type>()
来获取数组元素的类型,然后通过递归函数模板 get_array_dim
逐层解析方括号。下面举一个简单的例子,获取int[2][3]
的类型名称。
2.1.5 获取参数列表中多个类型的类型名称
template <typename... T>
std::string type_list_to_string() {
std::string result;
auto unused = {(result += type_name<T>() + ", ", 0)..., 0};
static_cast<void>(unused);
#if DBG_MACRO_CXX_STANDARD >= 17
if constexpr (sizeof...(T) > 0) {
#else
if (sizeof...(T) > 0) {
#endif
//去除result最终多余的空格和逗号
result.pop_back();
result.pop_back();
}
return result;
}
template <typename... T>
std::string get_type_name(type_tag<std::tuple<T...>>) {
return "std::tuple<" + type_list_to_string<T...>() + ">";
}
根据1.2.4章节介绍的折叠表达式,上述代码并不难理解。上述代码还有一个很有趣的部分是static_cast<void>(unused)
static_cast<void>(expression)
的作用是丢弃expression的值,只计算它的副作用(side effect)。副作用是指表达式在计算过程中对程序状态造成的改变,例如修改变量的值、调用函数、抛出异常等。
例如,如果我们有一个函数:
int f(int x)
{
std::cout << "f is called with x = " << x << std::endl;
return x * 2;
}
这个函数会打印出它的参数x,并返回x的两倍。如果我们写:
int y = f(10);
那么程序会输出:
f is called with x = 10
并且把y赋值为20。
但是如果我们写:
static_cast<void>(f(10));
那么程序只会输出:
f is called with x = 10
并且不会把返回值赋给任何变量,也不会对返回值做任何操作。
static_cast<void>(expression)
的作用有以下几个方面:
- 它可以用来消除编译器关于未使用的变量或者返回值的警告。
- 它可以用来强调表达式只是为了产生副作用而不是为了得到值。
- 它可以用来模拟C语言中的(void)0,表示一个空语句。
2.2 is_container
的实现
在打印变量的之的时候,普通变量和容器类型、容器适配器的变量的打印方式是不同的,普通变量直接打印就行,容器变量需要对容器进行遍历,对每一个元素进行打印。因此实现一个判断类型是否为容器或容器适配器的type_traits就很有用。
2.2.1 is_detected
// Implementation of 'is_detected' to specialize for container-like types
namespace detail_detector { //内部命名空间,向用户屏蔽实现细节
struct nonesuch { //一个空类,禁止拷贝、构造、析构,主要用于指示非容器类的类型
nonesuch() = delete;
~nonesuch() = delete;
nonesuch(nonesuch const&) = delete;
void operator=(nonesuch const&) = delete;
};
template <typename...>
using void_t = void; //SFINAE
template <class Default,
class AlwaysVoid,
template <class...>
class Op,
class... Args>
struct detector {
using value_t = std::false_type;
using type = Default;
}; //通用情况,Op<Args...>无法满足
template <class Default, template <class...> class Op, class... Args>
struct detector<Default, void_t<Op<Args...>>, Op, Args...> { //特化情况,满足Op<Args...>
using value_t = std::true_type;
using type = Op<Args...>;
};
} // namespace detail_detector
template <template <class...> class Op, class... Args>
using is_detected = typename detail_detector::
detector<detail_detector::nonesuch, void, Op, Args...>::value_t; //对外提供的接口
上述代码使用了SFINAE机制,如1.2.2节所介绍的那样。容易分析出,is_detected
的作用是判断Op<Args...>
这样的表述是否能通过编译。如果通过编译value_t = std::true_type
否则value_t = std::false_type
。
2.2.2 is_container
有起始迭代器、终止迭代器有大小(size)且不是string的类型就是容器。
namespace detail {
namespace {
using std::begin;
using std::end;
using std::size;
#endif
} // namespace
template <typename T>
struct remove_cvref { //去除类型的cv、引用限定符
typedef typename std::remove_cv<typename std::remove_reference<T>::type>::type
type;
};
template <typename T>
using remove_cvref_t = typename remove_cvref<T>::type;
template <typename T>
using detect_begin_t = decltype(detail::begin(std::declval<T>()));
template <typename T>
using detect_end_t = decltype(detail::end(std::declval<T>()));
template <typename T>
using detect_size_t = decltype(detail::size(std::declval<T>()));
template <typename T>
struct is_container {
static constexpr bool value =
is_detected<detect_begin_t, T>::value && //该类有起始迭代器
is_detected<detect_end_t, T>::value && //该类有终止迭代器
is_detected<detect_size_t, T>::value && //该类有大小
!std::is_same<std::string, remove_cvref_t<T>>::value; //该类不是string
};
}
下面就is_detected<detect_begin_t, T>::value
进行说明,根据2.2.1节,is_detected
的作用是判断Op<Args...>
这样的表述是否能通过编译。则上式就是判断detect_begin<T>
能否通过编译,根据detect_begin
的定义展开,上式即用于判断decltype(detail::begin(std::declval<T>()))
能否通过编译。当T
为int
时begin(int{})编译报错,此时is_detected<detect_begin_t, T>::value
为false
,当T
为vector<int>
时begin(int{})编译成功,此时is_detected<detect_begin_t, T>::value
为true
。
is_detected<detect_end_t, T>::value
和is_detected<detect_size_t, T>::value
的原理同上。现在我们就可以使用is_container
来判断一个类型是否是容器。
2.2.3 is_container_adapter
is_container_adapter
用于判断一个类型是否是容器适配器。实现如下。
template <typename T>
using detect_underlying_container_t =
typename remove_cvref_t<T>::container_type;
template <typename T>
struct is_container_adapter {
static constexpr bool value =
is_detected<detect_underlying_container_t, T>::value;
};
2.3 变量内容的输出
2.3.1 整形数字的16进制、8进制等格式支持
template <typename T>
struct print_formatted {
static_assert(std::is_integral<T>::value,
"Only integral types are supported.");
print_formatted(T value, int numeric_base)
: inner(value), base(numeric_base) {}
operator T() const { return inner; }
const char* prefix() const {
switch (base) {
case 8:
return "0o";
case 16:
return "0x";
case 2:
return "0b";
default:
return "";
}
}
T inner;
int base;
};
template <typename T>
print_formatted<T> hex(T value) {
return print_formatted<T>{value, 16};
}
template <typename T>
print_formatted<T> oct(T value) {
return print_formatted<T>{value, 8};
}
template <typename T>
print_formatted<T> bin(T value) {
return print_formatted<T>{value, 2};
}
2.3.2 容器适配器元素访问方式特化
namespace detail{
// Specializations for container adapters
//note
template <class T, class C>
T pop(std::stack<T, C>& adapter) {
T value = std::move(adapter.top());
adapter.pop();
return value;
}
template <class T, class C>
T pop(std::queue<T, C>& adapter) {
T value = std::move(adapter.front());
adapter.pop();
return value;
}
template <class T, class C, class Cmp>
T pop(std::priority_queue<T, C, Cmp>& adapter) {
T value = std::move(adapter.top());
adapter.pop();
return value;
}
}
2.3.3 has_ostream_operator
用于判断某个类型是否可以通过ostream
进行输出。同样是利用is_detected
进行实现。
template <typename T>
using ostream_operator_t =
decltype(std::declval<std::ostream&>() << std::declval<T>());
template <typename T>
struct has_ostream_operator : is_detected<ostream_operator_t, T> {};
2.3.4 pretty_print
的前置声明
// Helper to dbg(…)-print types
template <typename T>
struct print_type {};
template <typename T>
print_type<T> type() {
return print_type<T>{};
}
// Forward declarations of "pretty_print"
template <typename T>
inline void pretty_print(std::ostream& stream, const T& value, std::true_type);//T能够输出
template <typename T>
inline void pretty_print(std::ostream&, const T&, std::false_type);//T无法输出
//返回值表示是否打印表达式
template <typename T>
inline typename std::enable_if<
!detail::is_container<const T&>::value &&
!detail::is_container_adapter<const T&>::value &&
!std::is_enum<T>::value,
bool>::type
pretty_print(std::ostream& stream, const T& value);//T为非容器、非适配器、非enum
inline bool pretty_print(std::ostream& stream, const bool& value);//bool值的输出
inline bool pretty_print(std::ostream& stream, const char& value);//char值的输出
template <typename P>
inline bool pretty_print(std::ostream& stream, P* const& value);//指针类型的输出
template <typename T, typename Deleter>
inline bool pretty_print(std::ostream& stream,
std::unique_ptr<T, Deleter>& value); //智能指针unique_ptr的输出
template <typename T>
inline bool pretty_print(std::ostream& stream, std::shared_ptr<T>& value);
template <size_t N>
inline bool pretty_print(std::ostream& stream, const char (&value)[N]);//char数组的输出
template <>
inline bool pretty_print(std::ostream& stream, const char* const& value);//char*字符串的输出
template <typename... Ts>
inline bool pretty_print(std::ostream& stream, const std::tuple<Ts...>& value);//tuple的输出
template <>
inline bool pretty_print(std::ostream& stream, const std::tuple<>&);//空tuple的输出
template <>
inline bool pretty_print(std::ostream& stream, const time&);//时间的输出
template <typename T>
inline bool pretty_print(std::ostream& stream, const print_formatted<T>& value);//整形多种进制格式的输出
template <typename T>
inline bool pretty_print(std::ostream& stream, const print_type<T>&);//类型信息的输出
template <typename Enum>
inline typename std::enable_if<std::is_enum<Enum>::value, bool>::type
pretty_print(std::ostream& stream, Enum const& value); //enum的输出
inline bool pretty_print(std::ostream& stream, const std::string& value);//string的输出
#if DBG_MACRO_CXX_STANDARD >= 17
inline bool pretty_print(std::ostream& stream, const std::string_view& value);//string_view的输出
#endif
template <typename T1, typename T2>
inline bool pretty_print(std::ostream& stream, const std::pair<T1, T2>& value);//pair的输出
#if DBG_MACRO_CXX_STANDARD >= 17
template <typename T>
inline bool pretty_print(std::ostream& stream, const std::optional<T>& value);//optional的输出
template <typename... Ts>
inline bool pretty_print(std::ostream& stream,
const std::variant<Ts...>& value);//variant的输出
#endif
template <typename Container>
inline typename std::enable_if<detail::is_container<const Container&>::value,
bool>::type
pretty_print(std::ostream& stream, const Container& value);//容器内容的输出
template <typename ContainerAdapter>
inline typename std::enable_if<
detail::is_container_adapter<const ContainerAdapter&>::value,
bool>::type
pretty_print(std::ostream& stream, ContainerAdapter value);//容器适配器内容的输出
2.3.5 pretty_print
的具体实现
- 基础类型
template <typename T>
inline void pretty_print(std::ostream& stream, const T& value, std::true_type) {
stream << value;//直接打印
}
- 无法打印的类型
template <typename T>
inline void pretty_print(std::ostream&, const T&, std::false_type) {
static_assert(detail::has_ostream_operator<const T&>::value,
"Type does not support the << ostream operator");
}
- 非容器、非适配器、非enum类型
template <typename T>
inline typename std::enable_if<
!detail::is_container<const T&>::value &&
!detail::is_container_adapter<const T&>::value &&
!std::is_enum<T>::value,
bool>::type //SFINAE过滤特定类型
pretty_print(std::ostream& stream, const T& value) {
pretty_print(stream, value, typename detail::has_ostream_operator<const T&>::type{});
return true;
}
- 布尔类型
inline bool pretty_print(std::ostream& stream, const bool& value) {
stream << std::boolalpha << value;//以“true”,“false”的格式输出
return true;
}
- 字符类型
inline bool pretty_print(std::ostream& stream, const char& value) {
const bool printable = value >= 0x20 && value <= 0x7E;//只有部分ASCII字符可以打印
if (printable) {
stream << "'" << value << "'"; //可以印字符直接打印
} else {
stream << "'\\x" << std::setw(2) << std::setfill('0') << std::hex
<< std::uppercase << (0xFF & value) << "'";//不可打印字符直接输出16进制数字
}
return true;
}
- 指针类型
template <typename P>
inline bool pretty_print(std::ostream& stream, P* const& value) {
if (value == nullptr) {
stream << "nullptr";
} else {
stream << value;
}
return true;
}
- 智能指针
template <typename T, typename Deleter>
inline bool pretty_print(std::ostream& stream,
std::unique_ptr<T, Deleter>& value) {
pretty_print(stream, value.get());
return true;
}
template <typename T>
inline bool pretty_print(std::ostream& stream, std::shared_ptr<T>& value) {
pretty_print(stream, value.get());
stream << " (use_count = " << value.use_count() << ")";//输出引用计数
return true;
}
- 字符串类型(三类,char[], char*, std::string)
template <size_t N>
inline bool pretty_print(std::ostream& stream, const char (&value)[N]) {
stream << value;
return false;
}
template <>
inline bool pretty_print(std::ostream& stream, const char* const& value) {
stream << '"' << value << '"';
return true;
}
inline bool pretty_print(std::ostream& stream, const std::string& value) {
stream << '"' << value << '"';
return true;
}
#if DBG_MACRO_CXX_STANDARD >= 17
inline bool pretty_print(std::ostream& stream, const std::string_view& value) {
stream << '"' << std::string(value) << '"';
return true;
}
#endif
- tuple
pretty_print_tuple
递归函数模板实现。
template <size_t Idx>
struct pretty_print_tuple {
template <typename... Ts>
static void print(std::ostream& stream, const std::tuple<Ts...>& tuple) {
pretty_print_tuple<Idx - 1>::print(stream, tuple);
stream << ", ";
pretty_print(stream, std::get<Idx>(tuple));
}
};
template <>
struct pretty_print_tuple<0> {
template <typename... Ts>
static void print(std::ostream& stream, const std::tuple<Ts...>& tuple) {
pretty_print(stream, std::get<0>(tuple));
}
};
template <typename... Ts>
inline bool pretty_print(std::ostream& stream, const std::tuple<Ts...>& value) {
stream << "{";
pretty_print_tuple<sizeof...(Ts) - 1>::print(stream, value);
stream << "}";
return true;
}
template <>
inline bool pretty_print(std::ostream& stream, const std::tuple<>&) {
stream << "{}";
return true;
}
- 时间
利用std::chrono
时钟库实现。
template <>
inline bool pretty_print(std::ostream& stream, const time&) {
using namespace std::chrono;
const auto now = system_clock::now();
const auto us =
duration_cast<microseconds>(now.time_since_epoch()).count() % 1000000;
const auto hms = system_clock::to_time_t(now);
const std::tm* tm = std::localtime(&hms);
stream << "current time = " << std::put_time(tm, "%H:%M:%S") << '.'
<< std::setw(6) << std::setfill('0') << us;
return false;
}
- 整形的多进制
template <typename T>
std::string decimalToBinary(T n) {//将十进制数转为二进制字符串
const size_t length = 8 * sizeof(T);
std::string toRet;
toRet.resize(length);
for (size_t i = 0; i < length; ++i) {
const auto bit_at_index_i = static_cast<char>((n >> i) & 1);
toRet[length - 1 - i] = bit_at_index_i + '0';
}
return toRet;
}
template <typename T>
inline bool pretty_print(std::ostream& stream,
const print_formatted<T>& value) {
if (value.inner < 0) {//处理负数
stream << "-";
}
stream << value.prefix();//格式化前缀
// Print using setbase
if (value.base != 2) {
stream << std::setw(sizeof(T)) << std::setfill('0')
<< std::setbase(value.base) << std::uppercase;
if (value.inner >= 0) {
// The '+' sign makes sure that a uint_8 is printed as a number
stream << +value.inner;
} else {
using unsigned_type = typename std::make_unsigned<T>::type;
stream << +(static_cast<unsigned_type>(-(value.inner + 1)) + 1);//防越界
}
} else {
// Print for binary
if (value.inner >= 0) {
stream << decimalToBinary(value.inner);
} else {
using unsigned_type = typename std::make_unsigned<T>::type;
stream << decimalToBinary<unsigned_type>(
static_cast<unsigned_type>(-(value.inner + 1)) + 1); //防越界
}
}
return true;
}
- 单独打印类型信息
template <typename T>
inline bool pretty_print(std::ostream& stream, const print_type<T>&) {
stream << type_name<T>();
stream << " [sizeof: " << sizeof(T) << " byte, ";
stream << "trivial: ";
if (std::is_trivial<T>::value) {
stream << "yes";
} else {
stream << "no";
}
stream << ", standard layout: ";
if (std::is_standard_layout<T>::value) {
stream << "yes";
} else {
stream << "no";
}
stream << "]";
return false;
}
- enum类型
template <typename Enum>
inline typename std::enable_if<std::is_enum<Enum>::value, bool>::type
pretty_print(std::ostream& stream, Enum const& value) {
using UnderlyingType = typename std::underlying_type<Enum>::type;
stream << static_cast<UnderlyingType>(value);
return true;
}
- pair类型
template <typename T1, typename T2>
inline bool pretty_print(std::ostream& stream, const std::pair<T1, T2>& value) {
stream << "{";
pretty_print(stream, value.first);
stream << ", ";
pretty_print(stream, value.second);
stream << "}";
return true;
}
- cpp17新类型(optional,variant)
template <typename T>
inline bool pretty_print(std::ostream& stream, const std::optional<T>& value) {
if (value) {
stream << '{';
pretty_print(stream, *value);
stream << '}';
} else {
stream << "nullopt";
}
return true;
}
template <typename... Ts>
inline bool pretty_print(std::ostream& stream, const std::variant<Ts...>& value) {
stream << "{";
std::visit([&stream](auto&& arg) { pretty_print(stream, arg); }, value);
stream << "}";
return true;
}
- 容器类型
迭代器遍历。最多打印10个值。
template <typename Container>
inline typename std::enable_if<detail::is_container<const Container&>::value,
bool>::type
pretty_print(std::ostream& stream, const Container& value) {//引用传递
stream << "{";
const size_t size = detail::size(value);
const size_t n = std::min(size_t{10}, size);
size_t i = 0;
using std::begin;
using std::end;
for (auto it = begin(value); it != end(value) && i < n; ++it, ++i) {
pretty_print(stream, *it);
if (i != n - 1) {
stream << ", ";
}
}
if (size > n) {
stream << ", ...";
stream << " size:" << size;
}
stream << "}";
return true;
}
- 容器适配器类型
template <typename ContainerAdapter>
inline typename std::enable_if<
detail::is_container_adapter<const ContainerAdapter&>::value,
bool>::type
pretty_print(std::ostream& stream, ContainerAdapter value) {//值传递
stream << "{";
const size_t size = detail::size(value);
const size_t n = std::min(size_t{10}, size);
std::vector<typename ContainerAdapter::value_type> elements;
elements.reserve(n);
for (size_t i = 0; i < n; ++i) {
elements.push_back(detail::pop(value));
}
std::reverse(elements.begin(), elements.end());
if (size > n) {
stream << "..., ";
}
for (size_t i = 0; i < n; ++i) {
pretty_print(stream, elements[i]);
if (i != n - 1) {
stream << ", ";
}
}
if (size > n) {
stream << " (size:" << size << ")";
}
stream << "}";
return true;
}
2.4 DebugOutput
,dbg(…)的总体输出
2.4.1 获取参数包的最后一个参数的类型
template <typename T, typename... U>
struct last {
using type = typename last<U...>::type;
};
template <typename T>
struct last<T> {
using type = T;
};
template <typename... T>
using last_t = typename last<T...>::type;
递归类模板,last
可以获得参数包的最后一个参数的类型,如last_t<int, double, float>
的结果为float
。
2.4.2 判断设备是否支持彩色输出
inline bool isColorizedOutputEnabled() {
#if defined(DBG_MACRO_FORCE_COLOR)
return true;
#elif defined(DBG_MACRO_UNIX)
return isatty(fileno(stderr));
#else
return true;
#endif
}
2.4.3 DebugOutput
的实现
a. DebugOutput
类结构
class DebugOutput {
public:
// Helper alias to avoid obscure type `const char* const*` in signature.
using expr_t = const char*;
using expr_iterator = typename std::initializer_list<expr_t>::const_iterator;
using type_iterator = typename std::initializer_list<std::string>::const_iterator;
DebugOutput(const char* filepath, int line,
const char* function_name){}//构造函数,根据信息生成格式化的m_location字符串
template <typename... T>
auto print(std::initializer_list<expr_t> exprs,
std::initializer_list<std::string> types,
T&&... values) -> last_t<T...> {} //打印函数向外部提供的接口
private:
template <typename T>
T&& print_impl(expr_iterator expr, type_iterator type, T&& value) {} //打印函数的具体实现
template <typename T, typename... U>
auto print_impl(expr_iterator exprs, type_iterator types, T&& value,U&&... rest)
-> last_t<T, U...> {} 打印函数支持参数包的实现
const char* ansi(const char* code) const {} //根据是否支持彩色输出转换ansi控制字符
const bool m_use_colorized_output; //是否使用彩色输出
std::string m_location;//格式化的代码和文件信息,格式为:[文件地址:代码行数(函数名)]
static constexpr std::size_t MAX_PATH_LENGTH = 20;//最大地址长度
//ansi控制字符
static constexpr const char* const ANSI_EMPTY = "";//空白
static constexpr const char* const ANSI_DEBUG = "\x1b[02m"; //灰色
static constexpr const char* const ANSI_WARN = "\x1b[33m"; //黄色
static constexpr const char* const ANSI_EXPRESSION = "\x1b[36m"; //蓝色
static constexpr const char* const ANSI_VALUE = "\x1b[01m"; //白色
static constexpr const char* const ANSI_TYPE = "\x1b[32m"; //绿色
static constexpr const char* const ANSI_RESET = "\x1b[0m"; //清除格式
};
b. DebugOutput
的具体实现
- 构造函数的实现
DebugOutput(const char* filepath, int line, const char* function_name)//构造函数
: m_use_colorized_output(isColorizedOutputEnabled()) {
std::string path = filepath;//当前文件的地址
const std::size_t path_length = path.length();
if (path_length > MAX_PATH_LENGTH) {//地址过长,裁剪一部分
path = ".." + path.substr(path_length - MAX_PATH_LENGTH, MAX_PATH_LENGTH);
}
//按照格式:[文件地址:代码行数(函数名)] 生成字符串。且颜色为灰色
std::stringstream ss;
ss << ansi(ANSI_DEBUG) << "[" << path << ":" << line << " ("
<< function_name << ")] " << ansi(ANSI_RESET);
m_location = ss.str();
}
- ansi函数
const char* ansi(const char* code) const {
if (m_use_colorized_output) {//支持彩色打印,返回输入
return code;
} else {
return ANSI_EMPTY; //不支持彩色打印,返回空串
}
}
print_impl
的实现
template <typename T>
T&& print_impl(expr_iterator expr, type_iterator type, T&& value) {
const T& ref = value;
std::stringstream stream_value;//变量数据的字符串流
const bool print_expr_and_type = pretty_print(stream_value, ref);//将变量的值输出到stream_value
std::stringstream output;//整体输出
output << m_location;//格式化的代码和文件信息
if (print_expr_and_type) {//输出表达式
output << ansi(ANSI_EXPRESSION) << *expr << ansi(ANSI_RESET) << " = ";
}
output << ansi(ANSI_VALUE) << stream_value.str() << ansi(ANSI_RESET);//输出变量的值
if (print_expr_and_type) {//输出表达式类型
output << " (" << ansi(ANSI_TYPE) << *type << ansi(ANSI_RESET) << ")";
}
output << std::endl;
std::cerr << output.str();
return std::forward<T>(value);//完美转发,返回变量
}
template <typename T, typename... U>
auto print_impl(expr_iterator exprs,
type_iterator types,
T&& value,
U&&... rest) -> last_t<T, U...> {
print_impl(exprs, types, std::forward<T>(value));//initializer_list的类型为最后一个表达式的类型
return print_impl(exprs + 1, types + 1, std::forward<U>(rest)...);
}
- 接口
print
的实现
class DebugOutput {
template <typename... T>
auto print(std::initializer_list<expr_t> exprs, //表达式列表
std::initializer_list<std::string> types, //类型列表
T&&... values) -> last_t<T...> {//initializer_list的最终类型为最后一个表达式的类型
if (exprs.size() != sizeof...(values)) {
std::cerr
<< m_location << ansi(ANSI_WARN)
<< "The number of arguments mismatch, please check unprotected comma"
<< ansi(ANSI_RESET) << std::endl;
}
return print_impl(exprs.begin(), types.begin(), std::forward<T>(values)...);
}
};
2.5 dbg(…)
宏定义
2.5.1 避免"-Wunused-value" warnings
template <typename T>
T&& identity(T&& t) {
return std::forward<T>(t);
}
template <typename T, typename... U>
auto identity(T&&, U&&... u) -> last_t<U...> {
return identity(std::forward<U>(u)...);
}
2.5.2 一系列宏定义
a.DBG_CALL
与DBG_CAT
,函数调用与拼接
#define DBG_IDENTITY(x) x
#define DBG_CALL(fn, args) DBG_IDENTITY(fn args) //函数调用宏
#define DBG_CAT_IMPL(_1, _2) _1##_2
#define DBG_CAT(_1, _2) DBG_CAT_IMPL(_1, _2) //宏拼接
例如:DBG_CALL(fn, (args))
展开为 fn(args)
;DBG_CAT(a,b)
展开为ab
b. DBG_16TH_IMPL
抽取第16个参数
#define DBG_16TH_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, \
_14, _15, _16, ...) \
_16
#define DBG_16TH(args) DBG_CALL(DBG_16TH_IMPL, args)
例如:DBG_16TH((1,2,3,4,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0))
将展开为:
DBG_16TH_IMPL(1,2,3,4,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)
将展开为4
(第16个参数)
c. DBG_NARGS
宏参数数量计数
#define DBG_NARG(...) \
DBG_16TH((__VA_ARGS__, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
例如:DBG_NARG(a,b,c)
首先展开为:
DBG_16TH((a, b, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
然后展开为:
DBG_16TH_IMPL(a, b, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
将展开为
然后再一次展开为3
。
因此这个宏可以完成宏参数数量在16以下的计数。
d. DBG_VARIADIC_CAL
函数生成宏
// DBG_VARIADIC_CALL(fn, data, e1, e2, ...) => fn_N(data, (e1, e2, ...))
#define DBG_VARIADIC_CALL(fn, data, ...) \
DBG_CAT(fn##_, DBG_NARG(__VA_ARGS__))(data, (__VA_ARGS__))
例如:DBG_VARIADIC_CALL(fn, data, e1, e2, ...)
将展开为 fn_N(data, (e1, e2, ...))
e. DBG_HEAD
和DBG_TAIL
参数包头部和后续序列的获取宏
// (e1, e2, e3, ...) => e1
#define DBG_HEAD_IMPL(_1, ...) _1
#define DBG_HEAD(args) DBG_CALL(DBG_HEAD_IMPL, args)
// (e1, e2, e3, ...) => (e2, e3, ...)
#define DBG_TAIL_IMPL(_1, ...) (__VA_ARGS__)
#define DBG_TAIL(args) DBG_CALL(DBG_TAIL_IMPL, args)
例如:DBG_HEAD(e1, e2, e3, ...)
将展开为e1
DBG_TAIL(e1, e2, e3, ...)
将展开为 (e2, e3, ...)
f. DBG_MAP
,高级函数map的宏实现
#define DBG_MAP_1(fn, args) DBG_CALL(fn, args)
#define DBG_MAP_2(fn, args) fn(DBG_HEAD(args)), DBG_MAP_1(fn, DBG_TAIL(args))
#define DBG_MAP_3(fn, args) fn(DBG_HEAD(args)), DBG_MAP_2(fn, DBG_TAIL(args))
#define DBG_MAP_4(fn, args) fn(DBG_HEAD(args)), DBG_MAP_3(fn, DBG_TAIL(args))
#define DBG_MAP_5(fn, args) fn(DBG_HEAD(args)), DBG_MAP_4(fn, DBG_TAIL(args))
#define DBG_MAP_6(fn, args) fn(DBG_HEAD(args)), DBG_MAP_5(fn, DBG_TAIL(args))
#define DBG_MAP_7(fn, args) fn(DBG_HEAD(args)), DBG_MAP_6(fn, DBG_TAIL(args))
#define DBG_MAP_8(fn, args) fn(DBG_HEAD(args)), DBG_MAP_7(fn, DBG_TAIL(args))
#define DBG_MAP_9(fn, args) fn(DBG_HEAD(args)), DBG_MAP_8(fn, DBG_TAIL(args))
#define DBG_MAP_10(fn, args) fn(DBG_HEAD(args)), DBG_MAP_9(fn, DBG_TAIL(args))
#define DBG_MAP_11(fn, args) fn(DBG_HEAD(args)), DBG_MAP_10(fn, DBG_TAIL(args))
#define DBG_MAP_12(fn, args) fn(DBG_HEAD(args)), DBG_MAP_11(fn, DBG_TAIL(args))
#define DBG_MAP_13(fn, args) fn(DBG_HEAD(args)), DBG_MAP_12(fn, DBG_TAIL(args))
#define DBG_MAP_14(fn, args) fn(DBG_HEAD(args)), DBG_MAP_13(fn, DBG_TAIL(args))
#define DBG_MAP_15(fn, args) fn(DBG_HEAD(args)), DBG_MAP_14(fn, DBG_TAIL(args))
#define DBG_MAP_16(fn, args) fn(DBG_HEAD(args)), DBG_MAP_15(fn, DBG_TAIL(args))
// DBG_MAP(fn, e1, e2, e3, ...) => fn(e1), fn(e2), fn(e3), ...
#define DBG_MAP(fn, ...) DBG_VARIADIC_CALL(DBG_MAP, fn, __VA_ARGS__)
例如:DBG_MAP(fn, e1, e2, e3)
首先展开为:
DBG_VARIADIC_CALL(DBG_MAP, fn, e1, e2, e3)
,然后展开为:
DBG_MAP_3(fn, e1, e2, e3)
,然后继续展开:
fn(e1), DBG_MAP_2(fn, e2, e3)
, 继续:
fn(e1), fn(e2), DBG_MAP_1(fn, e3)
,继续:
fn(e1), fn(e2), fn(e3)
。
综上所述:DBG_MAP(fn, e1, e2, e3, ...)
将展开为fn(e1), fn(e2), fn(e3), ...
;
g. DBG_STRINGIFY
和DBG_TYPE_NAME
获取表达式字面字符串和表达式类型名称串的宏
#define DBG_STRINGIFY_IMPL(x) #x
#define DBG_STRINGIFY(x) DBG_STRINGIFY_IMPL(x)
#define DBG_TYPE_NAME(x) dbg::type_name<decltype(x)>()
h. dbg(…)
宏
#define dbg(...) \
dbg::DebugOutput(__FILE__, __LINE__, __func__) \
.print({DBG_MAP(DBG_STRINGIFY, __VA_ARGS__)}, \
{DBG_MAP(DBG_TYPE_NAME, __VA_ARGS__)}, __VA_ARGS__)
例如:dbg(a+b, a, b)
首先展开为(假定cpp文件为main.cpp
,行数为10
,函数名为main
, a,b
为整形变量):
dbg::DebugOutput("main.cpp", 10, "main") \
.print({DBG_MAP(DBG_STRINGIFY, a+b, a, b)}, \
{DBG_MAP(DBG_TYPE_NAME, a+b, a, b)}, a+b, a, b)
然后展开为:
dbg::DebugOutput("main.cpp", 10, "main") \
.print({DBG_STRINGIFY(a+b), DBG_STRINGIFY(a), DBG_STRINGIFY(b)}, \
{DBG_TYPE_NAME(a+b), DBG_TYPE_NAME(a), DBG_TYPE_NAME(b)}, \
a+b, a, b)
最后展开为:
dbg::DebugOutput("main.cpp", 10, "main") \
.print({"a+b", "a", "b"}, \
{"int32_t", "int32_t", "int32_t"}, \
a+b, a, b)
!!完结撒花★,°:.☆( ̄▽ ̄)/$:.°★ 。