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]