Skip to content

Latest commit

 

History

History
114 lines (99 loc) · 5.72 KB

note.c++.template_param_package.md

File metadata and controls

114 lines (99 loc) · 5.72 KB
id title desc updated created
vzez3ilwus6l6lgyirixyvo
Template_param_package
1700631007896
1700532167019

scene

一个自定义的单例日志模块,此前的配置都是在单例的某个虚函数中实现的,此时配置属于模块,而模块被所有目标target共同引用,那么将会面临配置的冲突和重合

  1. 方案1: 每一个target进行日志引用时,继承基础单例,实现自己的日志模块,即生成新的对象(新模块)进行引用(本质上是一种静态的配置),存在问题,同一个target下的所有引用可以统一为新的日志对象,但target外的公告模块进行日志引用,将引用的日志基础类,引用存在冲突
  2. 方案2: 为了统一外部公共模块以及target内部模块日志的引用,首先确定了继承之路行不通,还是要统一一个公共引用,即同一个单例,并且同一个接口,在运行时通过某个接口进行动态配置或者在运行时传入回调,在第一次单例构造时callback进行自动配置。
    1. 配置时,需要进行不定个sink的生成以及注册,还有个别固定参数的传参
    2. 若在外部生成时则需要个别protect函数
      1. 重定义个别 gen 函数由外部调用
      2. 定义工厂函数由外部传参构造参数,实现内部的注册与构造
template 如何通过不定参来实现,一次性传递一系列包含n个相同对象的构造参数
不定参模板函数的展开方案
  1. 递归
  2. 逗号展开
void print() {
    printf("no args print\n");
}
void print(int arg) {
    printf("base print :%d\n", arg);
}
//recursive expand
template<typename Arg1, typename Arg2, typename ...Args>
void print(int fixed, Arg1 arg1, Arg2 arg2, Args... args) {
    print(fixed, args...);
}
//1 2 3 4 5
//1 3 5 
//1

//1 2 3 4
//1 4
//无法展开,不满足三个参数

//1 2 3
//1

//1
//无法展开,不满足三个参数

//规律: 参数>2, 并且为计数,因为每次展开消费两个arg,  递归到最后需要存在一个参数, 即必须是大于1的奇数
//comma expand
template<typename T>
void printarg(T t) {
    std::cout << t << std::endl;
}
template<typename ...Args>
void expand(Args... args) {
    int arr[] = {(printarg(args), 0)...};
}

int main(int argc, char** argv) {
    //template expand 
    expand(1, 2, 3, 4);
    print(1, 2, 3, 4, 5);
    print(1, 2, 3);
    print(1);
    return 0;
}
编译器对模板可变参递归展开的过程
模板不定参递归与函数递归

模板的目标: 通过编译期 模板引用时传参来指导编译器在模板定义处进行代码生成 递归生成一个完整的函数实现, 进而 在运行时直接引用,本质上是通过代码生成的语法强化接口功能 所以模板不定参的递归展开的关键:

  1. ...Args 在传递过程可以为0个参数,每次递归用当前形参不定参...Args重新作为实参填入,并占位原先的固定位置参数,从而实现...Args 一点点的缩短被提取到固定位置,直到匹配到特定函数递归结束
  2. 固定位置参数 是明确的,用来进行引用或者传递
  3. 关键在于 设计模板函数参数的固定位置参数和递归结束函数

不定参模板函数,逗号展开的关键:

  1. 可变参数模板的展开方法:
    1. 最简单,可变参形参后加...
     args...
     //展开的结果就是逗号分隔的一列实际参数
    1. 与args关联的表达式展开逻辑,在包含args的表达式中,会对实际的每一个参数进行表达式中的运算并使用逗号分隔,表达式中的args可以看作是实际参数的占位符
     auto tmp = { (args * args)...};
     auto tmp1 = { std::sin(args)...};
     auto tmp2 = { (std::sin(args) + args)...};
     //expand to
     auto tmp = {(1 * 1),(2 * 2),(3 * 3)};
     auto tmp1 = { std::sin(1), std::sin(2), std::sin(3) };
     auto tmp2 = { (std::sin(1) + 1), (std::sin(2) + 2), (std::sin(2) + 2) };
  2. 逗号表达式:
    1. t=(print(), c) 逗号表达式可以实现先执行逗号前表达式,然后返回逗号后表达式,赋值给t
    2. 连续多个逗号呢,auto a = (funA(), funB(), funC()); 所有表达式依次执行,最后只返回最后一个表达式的返回值
  3. 初始化列表:
    1. 元素必须是同类型
    2. 必须是确定类型,不能为void
    3. 为什么展开时使用初始化列表?
    4. std::initializer_list 与 {}: {}是语法表达式生成initializer_list类型的对象,而initializer_list通常表示形参时使用,来接受{}表达式的结果以及传递。

    首先,可变参数包需要展开,展开即需要入口(entry), 一个选择是作为实参传入函数接口中,另一个选择即是c++语法糖,初始化列表,通过一个{}组成的表达式,填入实参列表来直接初始化一个对象,故可理解为初始化列表在此作为参数包展开的容器,仅仅为了容纳而已 紧接上面的问题,第二个: 为什么是逗号表达式,答案在初始化列表本身的性质中,由于初始化列表要求元素同类型,那么对于参数包而言,所有实参展开后的类型都有可能不同,如何实现在初始化列表中展开 类型不同的实参?通过逗号表达式的技巧,使得即能执行每一个展开表达式,又能返回一个统一的类型或值

函数的递归: 在于编译期 编写判断条件,递归重复引用,在运行时 进行条件判断,最终跳出引用

refe

c++11之函数参数包展开 泛化之美--C++11可变模版参数的妙用 C++ 初始化列表展开