可变参数vs可变模板巧用

https://eli.thegreenplace.net/2014/variadic-templates-in-c/

可变参数

  • scanf函数和printf函数参数是可变的,可选参数的类型可以变化,可选参数的数量也可变化,可选参数前面必须至少有一个强制参数(确定参数)
  • 声明和定义时,强制参数后面跟着一个省略号…代表可选参数
  • 函数内部,使用stdarg.h头文件中相关结构方法获取参数
  • va_list类型变量用来接受可变参数列表,va_start宏来初始化va_list变量
  • va_arg宏和va_list变量来访问参数列表中的每个项
  • va_end宏清理va_list变量的内存
#include <stdarg.h>

int sum(int num, ...) {
  va_list va;
  va_start(va, num);
  int result = 0;
  for (int i = 0; i < num; ++i) {
    result += va_arg(va, int);
  }
  va_end(va);
  return result;
}

// gcc预处理-E结果
typedef __builtin_va_list __gnuc_va_list;
typedef __gnuc_va_list va_list;

int sum(int num, ...) {
  va_list va;

  __builtin_va_start(va, num);

  int result = 0;
  for (int i = 0; i < num; ++i) {
    result += __builtin_va_arg(va, int);
  }

  __builtin_va_end(va);
  return result;
}
  • 类型不安全,所有类型解析必须在va_arg中使用类型强制转换在运行时显式完成)
  • 正确进行类型解析也很棘手,va_宏执行低级内存操作
  • 把编译时清楚知道的东西留给运行时

可变模板

  • 变量模板是c++ 11的新特性之一
  • 可变模板可以编写函数,以类型安全的方式接受任意数量的参数,并在编译时而不是运行时解析所有参数处理逻辑
#include <iostream>
#include <string>

# 普通模板
template <typename T> T adder(T v) { return v; }

# 可变模板
template <typename T, typename... Args> T adder(T first, Args... args) {
  return first + adder(args...);
}

int main(int argc, char **argv) {
  std::cout << adder(std::string("a"), std::string("b"), std::string("c"))
            << std::endl;
  std::cout << adder(3, 3, 4, 5.1) << std::endl;
  std::cout << adder(3.1, 3, 4, 5) << std::endl;
  return 0;
}

// 运行结果
abc
15
15.1
  • adder将接受任意数量的参数,只要它可以对参数应用+操作符,就可以正确地编译,编译器在编译时执行此检查,它没有什么神奇之处——它遵循c++通常的模板和重载解析规则
  • typename... Args称为模板参数包,Args…args被称为函数参数包,可变参数模板的编写方式与编写递归代码一样,需要一个基本情况
  • 输出宏std::cout << __PRETTY_FUNCTION__ << "\n";可以清楚看到Args模板运行时具体情况
简单变化
  • 更有趣例子
template<typename T>
bool pair_comparer(T a, T b) {
  // In real-world code, we wouldn't compare floating point values like
  // this. It would make sense to specialize this function for floating
  // point types to use approximate comparison.
  return a == b;
}

template<typename T, typename... Args>
bool pair_comparer(T a, T b, Args... args) {
  return a == b && pair_comparer(args...);
}
  • 入参必须偶数个,成对类型相同,否则编译时报错
性能与安全
  • 不涉及实际递归,只是在编译时预生成函数调用序列,由于现代编译器积极地内联代码,最终很可能编译成完全没有函数调用的机器码,最终得到的其实和循环展开没什么不同
  • c风格的可变参数必须在运行时进行解析,va_宏实际上是在操作运行时堆栈,因此可变参数模板通常是对可变参数函数的性能优化
  • 由于是编译时实现,可变参数模板使我们能够编写类型安全的函数
Varidic数据结构
  • 可变参数模板可以定义具有任意数量字段的数据结构,并在每次使用时配置该数量的字段
template <class... Ts> struct tuple {};

template <class T, class... Ts>
struct tuple<T, Ts...> : tuple<Ts...> {
  tuple(T t, Ts... ts) : tuple<Ts...>(ts...), tail(t) {}

  T tail;
};

tuple<double, uint64_t, const char*> t1(12.2, 42, "big");
  • 忽略构造函数,创建的元组结构的伪跟踪
struct tuple<double, uint64_t, const char*> : tuple<uint64_t, const char*> {
  double tail;
}

struct tuple<uint64_t, const char*> : tuple<const char*> {
  uint64_t tail;
}

struct tuple<const char*> : tuple {
  const char* tail;
}

struct tuple {
}
  • 原始的3元素元组中数据成员的布局
[const char* tail, uint64_t tail, double tail]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值