可变参数模板

内容简介

可变参数模板允许模板接收任意数量的模板参数或函数参数。它是 std::make_uniquestd::make_shared、完美转发、格式化库、通用工厂函数的基础。

核心概念

template <typename... Args>
void func(Args... args)
{
}
  • typename... Args:模板参数包。
  • Args... args:函数参数包。
  • args...:展开参数包。

基础用法

打印任意数量参数:

template <typename T>
void print_one(const T& value)
{
    std::cout << value << '\n';
}
 
template <typename... Args>
void print_all(const Args&... args)
{
    (print_one(args), ...);  // C++17 折叠表达式
}
 
int main()
{
    print_all(1, "hello", 3.14);
}

递归展开写法

C++17 前常用递归展开:

void print_all()
{
}
 
template <typename T, typename... Rest>
void print_all(const T& first, const Rest&... rest)
{
    std::cout << first << '\n';
    print_all(rest...);
}

折叠表达式

C++17 提供折叠表达式,写起来更直接:

template <typename... Args>
auto sum(Args... args)
{
    return (args + ...);
}

带初始值:

template <typename... Args>
auto sum(Args... args)
{
    return (0 + ... + args);
}

和完美转发结合

通用工厂函数:

template <typename T, typename... Args>
std::unique_ptr<T> make_object(Args&&... args)
{
    return std::make_unique<T>(std::forward<Args>(args)...);
}

这里的重点:

  • Args&&... 是转发引用参数包。
  • std::forward<Args>(args)... 保留每个实参的左值/右值属性。
  • 这类写法和 完美转发 强相关。

最佳代码实践

  • 简单参数包展开优先用 C++17 折叠表达式。
  • 需要保留参数值类别时,使用 Args&&...std::forward<Args>(args)...
  • 参数包函数要保持语义清楚,不要写成看不懂的模板魔法。
  • 如果所有参数要求同一类型,std::initializer_list 或容器可能更简单。

错误用法

template <typename... Args>
void save(Args&&... args)
{
    use(args...);  // 错误倾向:丢失右值属性
}

更好的写法:

template <typename... Args>
void save(Args&&... args)
{
    use(std::forward<Args>(args)...);
}

注意事项

  • 可变参数模板强大但容易降低可读性。
  • 参数包展开位置很重要,args... 不是普通数组。
  • 如果看不懂 Args&&...,先学 右值引用完美转发