dbg_macro库代码逐行解析

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:

  1. 一元右折叠 (E 运算符 ...)成为 (E1 运算符 (... 运算符 (EN-1 运算符 EN)))

  2. 一元左折叠 (... 运算符 E)成为 (((E1 运算符 E2) 运算符 ...) 运算符 EN)

  3. 二元右折叠 (E 运算符 ... 运算符 I)成为 (E1 运算符 (... 运算符 (EN−1 运算符 (EN 运算符 I))))

  4. 二元左折叠 (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}

将一元折叠用于长度为零的包展开时,只能使用下列运算符:

  1. 逻辑与(&&)。空包的值是 true

  2. 逻辑或(||)。空包的值是 false

  3. 逗号运算符(,)。空包的值是 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>()))能否通过编译。当Tint时begin(int{})编译报错,此时is_detected<detect_begin_t, T>::valuefalse,当Tvector<int>时begin(int{})编译成功,此时is_detected<detect_begin_t, T>::valuetrue

is_detected<detect_end_t, T>::valueis_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_CALLDBG_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_HEADDBG_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_STRINGIFYDBG_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)

!!完结撒花★,°:.☆( ̄▽ ̄)/$:.°★
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王_日月_华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值