引入

C++中经常打印变量来调试代码,但无论是printf还是cout总是很麻烦:

  • printf

    int a = 1;
    float b = 2.0;
    char c = 'c';
    printf("a = %d, b = %f, c = %c", a, b, c);
    
  • cout

    int a = 1;
    float b = 2.0;
    char c = 'c';
    std::cout << "a = " << a << ", b = " << b << ", c = " << c << '\n';
    

可变参数宏

可变参数宏是C99引入的一个特性,C++11开始支持。

#define def_name(...) def_body(__VA_ARGS__)

可变参数模板

C++11允许模板定义有任意类型任意数量的模板参数(包括 $0$ 个模板参数)。

template<typename... Values> class tuple;

如果不希望有 $0$ 个模板实参,可以如下声明:

template<typename First, typename... Rest> class tuple;

可变参数模板适用于函数模板,例如printf函数的实现:

template<typename... Args>
void printf(const std::string &str_format, Args... args);

参数包的展开

  • 对于模板函数
    • 递归展开
    // 递归终止函数
    void print() {}
    
    template<typename T, typename... Args>
    void print(T head, Args... args) {
      std::cout << head << (sizeof...(args) > 0 ? ", " : "");
      print(args...);
    }
    
    • 逗号表达式展开:这种方式有点繁琐,不太好理解,不推荐
  • 对于模板类
    • 递归展开
    • 继承展开

宏运算符

  • #:将宏参数转换为字符串

    #include <stdio.h>
    #define stringer( x ) printf( #x "\n" )
    int main() {
      stringer( In quotes in the printf function call );
      stringer( "In quotes when printed to the screen" );
      stringer( "This: \"  prints an escaped double quote" );
    }
    

    使用gcc -E预处理之后,得到如下结果

    int main() {
      printf("In quotes in the printf function call" "\n");
      printf("\"In quotes when printed to the screen\"" "\n");
      printf("\"This: \\\"  prints an escaped double quote\"" "\n");
    }
    
  • #@:将参数用单引号括起来,作为一个字符处理

    #define makechar(x)  #@x
    c = makechar(b);
    

    预处理之后得到:

    c = 'b';
    
  • ##:将两个独立的token连接在一起

    #define paster(n) printf("token" #n " = %d", token##n)
    int token9 = 9;
    paster(9);
    

    预处理之后得到:

    printf("token9 = %d", token9);
    

实现DBG

掌握了以上技能,我们就可以实现一个简单的调试宏:

#include <iostream>
#include <string>

template <typename T>
void _DBG(const char *s, T t) {
  std::cout << s << "=" << t << '\n';
}

template <typename T, typename... Args>
void _DBG(const char *s, T t, Args... args) {
  while (*s != ',')
    std::cout << *s++;
  std::cout << "=" << t << ",";
  _DBG(s + 1, args...);
}

#define DBG(...) _DBG(#__VA_ARGS__, __VA_ARGS__)

int main() {
  int a = 1;
  float b = 2.0;
  char c = 'c';
  std::string d = "def";
  DBG(a, b, c, d);
  return 0;
}

上面的代码输出:

a=1, b=2, c=c, d=def 

References