文档目录

一、解决什么问题?

问题:在 C++03 中,你想写一个"接收任意数量参数的函数",只能靠:

  1. 函数重载(写 N 个版本,累死)。
  2. C 风格的 ...(va_args,类型不安全)。

Variadic Templates 的目标:类型安全的任意参数。

二、什么时候用它?

场景 例子
包装/转发函数参数 std::make_unique<T>(args...)、std::thread(func, args...)
实现类型安全的 printf std::format("{} {}", a, b)
构造 tuple std::tuple<int, double, string>(1, 2.0, "hello")
实现工厂模式 factory.create<Widget>(arg1, arg2, arg3)
日志库 log.info("user {} logged in at {}", name, time)

三、递归展开:编译器是怎么处理"任意参数"的?

// 你想写一个打印任意数量、任意类型参数的函数:
print(1, 2.0, "hello", 'c');

// Variadic Templates 的做法:递归 + 终止
void print() {}  // ① 终止函数:0 个参数时调用

template <typename T, typename... Args>
void print(T first, Args... rest) {
    std::cout << first << " ";  // 打印第一个
    print(rest...);              // ② 用剩余参数继续递归
}

编译器实例化的过程:

print(1, 2.0, "hello", 'c')
  编译器生成:
  void print<int, double, const char*, char>(int first, ...) {
      cout << first;          // 打印 1
      print<double, const char*, char>(2.0, "hello", 'c');
  }
  
  再生成:
  void print<double, const char*, char>(double first, ...) {
      cout << first;          // 打印 2.0
      print<const char*, char>("hello", 'c');
  }
  
  ... 直到:
  void print<>() { }  // 终止

关键理解:print(1, 2.0, "hello") 在编译期被展开为3 个不同的函数,每个函数处理一个参数。没有运行时循环,没有类型擦除,每个参数类型都保留。

四、你最常用的场景:完美转发 + Variadic

// make_unique 的简化实现
template <typename T, typename... Args>
std::unique_ptr<T> my_make_unique(Args&&... args) {
    return std::unique_ptr<T>(
        new T(std::forward<Args>(args)...)  // 包展开 + 完美转发
    );
}

auto ptr = my_make_unique<Widget>(1, 2.0, "config");
// 展开为:new Widget(std::forward<int>(1), std::forward<double>(2.0), std::forward<const char*>("config"))
// 每个参数的左值/右值属性都被完美保留

这里 std::forward<Args>(args)... 包展开做了两件事:

  1. 把 args 包展开为 arg1, arg2, arg3, ...。
  2. 对每个参数应用 std::forward,保持其左值/右值属性。

五、面试题:sizeof… 在什么时候用?

template <typename... Args>
void log(Args... args) {
    // sizeof...(Args) 是编译期常量
    if constexpr (sizeof...(Args) == 0) {
        std::cout << "no args\n";
    } else {
        // 有参数的处理
    }
}

面试官问:“sizeof… 能用在运行时吗?”

  • 不能。sizeof...(Args) 是编译期求值的,等价于编译器数一数模板参数的数量,然后替换成常量。运行时不能得到这个值。