美文网首页
可变参模板

可变参模板

作者: 404Not_Found | 来源:发表于2021-12-31 09:15 被阅读0次
  • 作者: 雪山肥鱼
  • 时间:20211228 07:32
  • 目的: 可变参模板

可变参模板 从 C++11 引入,允许模板定义中包含 0到多个 任意模板。而c++17 也有相关新的特性

可变参模板引入

template<typename... T>//...代表 参数包
void myfunc(T... args) {//T:一堆类型,类型包,args:一堆形参,形参包,每个参数的类型是可以不相同的
  cout << "--------begin---------" << endl;
  cout << sizeof...(args) << endl;//收到的参数数量,sizeof...() 针对可变参固定写法 c++11 引入,针对的只能是...的可变参,()可以是类型,也可以是形参
  cout << sizeof...(T) << endl;//收到的类型数
  cout << "-------end-------" << endl;
}

int main() {
  myfunc();
  myfunc(10,20);
  myfunc(10,25.8,"ab",68);
  myfunc<double, double>(10,25,8,"ab",68,73);//后面3个事编译器推断出来的类型

  return 0;
}

可变参模板的展开

可变参模板的展开套路比较固定。与C中的可变参略有不同。内部实现

c++11中的展开

/*
  template<typename... T>
  void myfunc(T... args) {

  }
*/
void myfunc() {
  cout<<"展开完毕"<<endl;
}

template<typename T, typename... U>
void myfunc(T fristarg, U...otherArgs) {
  cout<<"收到的参数"<<<"firstarg"<<firstarg<<endl;
  myfunc(otherArgs...);
}

int main() {
  myfunc(10, "abc",12.7);
  return 0;
}

可变参模板的展开,将类型T 和 参数 Args,整个挪到 template中。T 我认为在这里应该起到的是占位符的作用。因为 所有args的类型 不需要完全统一。

c++17 中的展开

涉及 if constexpr() 的使用

template<typename T, typename...U>
void myfunc(T firstarg, U...ortherAargs) {
  cout<<"收到的参数"<<"firstarg"<<firstArgs<<endl;
  cout<<"sizeof...(otherArgs)"<<sizeof...(otherAargs)<<endl;
  if constexpr (sizeof...(otherAargs)) {
    myfunc(otherAargs...);
  }
}

int main() {
  myfunc(10, "abc", 12.7);
  return 0;
}

// 输出结果,2, 1, 0 每进来一次 都会 -1

注意注释中关于sizeof...() 的 阐述。每次进来会-1
用dumpbin 可以查看到,这个模板被实例化了3次

myfunc<int, char const *, double>(int, const char *,double)
myfunc<const char *, double>(const char * ,double);
myfunc<double>(double)

关于 if constexpr

// 关于 if constexpr
/*
1.
    不管 if constexpr 条件是否成立,都会进行检查
    这不同于 #ifdef, 不成立,则不会进入编译
    if constexpr(sizeof...(otherargs) > 100) 
    {
        testfunc()
    }
*/
/*
2. 内容必须是常量,从执行期间,转移到了编译期间,则必须为常量
    指定不行
    int i = 0;
    if constexpr(i > 0) {
    }
*/

可变参模板

template<typename... T>
void myfunc(T... arg) {
    cout << "myfunc(T...arg) is called" << endl;
}

template<typename... T>
void myfunc(T*... arg)
{
    cout << "myfunc(T*...arg) is called" << endl;
}

void myfunc(int arg) {
    cout << "myfunc(int arg) is called" << endl;
}

int main() {
    //优先使用函数
    myfunc(NULL);//int arg
    myfunc(nullptr);//T arg
    myfunc((int*)nullptr);//T*arg
    return 0;
}

与函数模板一样,优先选择普通函数

折叠表达式

//可变参模板2 :折叠表达式 c++17 才引入
//特点:它与所有可变参有关,而不是与单独某个可变参有关,需要所有参数都参与计算

//1. 一元左折
template<typename... T>
auto sub_left(T... args) {
    return (... - args);//圆括号不能省略,否则出错
}

//2. 一元右折
template<typename... T>
auto sub_right(T...args) {
    return (args - ...);
}

//3. 二元左折,init 初始值
template<typename... T>
auto sub_left_t(T... args) {
    return (220 - ... - args); //其中2个符号必须相同
}

//4. 二元右折
template<typename... T>
auto sub_right_t(T... args) {
    return (args - ... - 220);
}

template<typename... T>
void print_val_left_b(T... args) {
    (cout << ... <<args);//一定要有括号//cout<< 返回cout,第一次返回10,第二次abc,依此类推
}

int main() {
    
    cout << sub_left(10, 20, 30, 40) << endl;//10-20-30-40 = -80;
    cout << sub_right(10, 20, 30, 40) << endl;// (30-40) = -10, 20-(-10) = 30, 10 -30 = -20
    cout << sub_left_t(10, 20, 30, 40) << endl;//120 (220-10) -20 -30 -40 = 120
    print_val_left_b(10, "abc", 30, "def");//10abc30def 串起来了
    cout << sub_left_t(10, 20, 30, 40) << endl;//120 (220-10) -20 -30 -40 = 120 (10 先被220 减)
    cout << sub_right_t(10, 20, 30, 40) << endl;//200  10 -(20-(30-(40-220))) 初始值 就是说 40 先减掉220

    return 0;
}

关注注释内容即可

/*
    legacy
    新需求: 每个都*2后 再求和
*/
template<typename... T>
auto print_result_1(T const &... args) {
    (cout << ... << args) << "结束" << endl;
    return (... + args);
}

template<typename... T>
auto print_result_2(T const &... args) {
    (cout << ... << args) << "结束" << endl;
    return (... + args);
}

template<typename... T>
void print_calc(T const&... args) {
    cout << print_result_2(2 * args...);//可变参表达式
}

int main() {
    print_calc(10, 20, 30, 40);
    return 0;
}

prinnt_result_2(2*args...)即 可变参表达式

可变参类模板

递归继承方式展开 可变参

template<typename... Args>
class myclass {
public:
    myclass() {
        printf("myclass 泛化 is called, this = %p\n", this);
    }

};

template<typename First, typename... Others>// 偏特化,单抽出来一个,与其他一包类型
class myclass<First, Others...> :private myclass<Others...> {
public:
    myclass() :m_i(0)
    {
        printf("myclass 偏特化 is called, this = %p, sizeof...(Others) = %d\n", this, sizeof...(Others));
    }

    First m_i;
};

int main() {
    //有3个,第一个为int, 那么剩下参数2个,一次类推,double 结束后,others 就为0.
    //实例化顺序,先实例化泛化,再依次特化
    myclass<int, float, double> myc;

    return 0;
}

对于泛化可以用一个前置声明

template<typename... Args> class myclass;// 前向声明,非定义,成功的条件没有用声明创建对象。主要使用特化版本

只要不实例化就行,因为我们主要使用的是特化版本

1.PNG

对于 这3个参数,从右往左拿,第一次拿出 double,others 为0, 第二次拿float, others 为1, 即double, 第三次拿 int, others 为2, 即 float,double

图片.png

因为把 double 拿走后,参数个数为0, 所以,当参数为0时,使用泛化的构造函数,顺序如下:


图片.png

但是
当存在0个模板参数的特殊类模板时,不会选择泛化,而是选择0模板参数的类模板构造函数。

template<typename... Args>
class myclass {
public:
    myclass() {
        printf("myclass 泛化 is called, this = %p\n", this);
    }

};

template<typename First, typename... Others>// 偏特化,单抽出来一个,与其他一包类型
class myclass<First, Others...> :private myclass<Others...> {
public:
    myclass() :m_i(0)
    {
        printf("myclass 偏特化 is called, this = %p, sizeof...(Others) = %d\n", this, sizeof...(Others));
    }

    First m_i;
};

//0 个参数
template<>
class myclass<>//特殊的特化版本,0个 模板参数,看起来像全特化,但不是全特化,可变参模板不存在全特化
{
public:
    myclass() {
        printf("myclass() 0 个模板参数的泛化版本执行了,this = %p\n", this);
    }
};

int main() {
    myclass<int, float, double> myc;

    return 0;
}
2.PNG
可变参数类模板,不存在全特化

带参数的构造函数

template<typename... Args>
class myclass {
public:
    myclass() {
        printf("myclass 泛化 is called, this = %p\n", this);
    }

};

template<typename First, typename... Others>
class myclass<First, Others...> :private myclass<Others...> {
public:
    myclass() :m_i(0)
    {
        printf("myclass 偏特化 is called, this = %p, sizeof...(Others) = %d\n", this, sizeof...(Others));
    }

    myclass(First parf, Others... paro) :m_i(parf) ,myclass<Others...> (paro...) {
        cout << "------beigin------" << endl;
        cout << "sizeof...(Others): " << sizeof...(paro)<<endl;
        printf("myclass::myclass(First parf, Others... paro)泛化版本执行了, this = %p\n", this);
        cout << "m_i = " << m_i << endl;
        cout << "-------end--------" << endl;
    }
    First m_i;
};

template<>
class myclass<>
{
public:
    myclass() {
        printf("myclass() 0 个模板参数的泛化版本执行了,this = %p\n", this);
    }
};

int main() {
    myclass<int, float, double> myc(12,14.5,16.0);

    return 0;
}
图片.png

其实就是递归继承。都去继承。特化,其实也就是 把一坨东西,拆成了,一个+一坨,然后不停的递归。因为是递归,所以第一次进去的,组后一个出来。

非类型模板参数

template <int... Args>
class myclass2
{
public:
    myclass2() {
        printf("myclass2() 泛化版本执行了, this = %p\n", this);
    }
};


template<int First, int ...Others>
class myclass2<First, Others...>:private myclass2<Others...> {//这里的First, Others...都是具体的值了
public:
    myclass2() {
        printf("myclass2() 非类型模板参数 构造函数 is called this:%p, siezof...(Others) = %d, First = %d\n", this, sizeof...(Others), First);
    }
};

int main() {
    myclass2<11, 12, 13> my;
    return 0;
}
图片.png

因为是 递归,所以参数从右往左。注意 此时在<11,12,13>都是具体的值,并不是类型啦。

模板模板参数包

//模板模板参数包的展开
template<
        typename T,
        template<typename> typename... Container>
class myclass3 {
public:
    myclass3() {
        printf("myclass3() 泛化版本 is called,this=%p\n", this);
    }
};

template<
        typename T,
        template<typename> typename FirstContainer,
        template<typename> typename... OtherContainers>
class myclass3<T, FirstContainer, OtherContainers...> :private myclass3<T, OtherContainers...> {
public:
    myclass3() {
        printf("myclass3() 模板模板参数的特例化 is called\n this = %p, sizeof...(OthersContainers)=%d\n", this, sizeof...(OtherContainers));
        m_containers.push_back(12);
    }

    FirstContainer<T> m_containers;
};

template<typename T, 
    template<typename> typename... Container>
class myclass3_3 :private myclass3<T, Container...> {
public:
    myclass3_3() {
        printf("myclass3_3() 继承构造 is called\n this = %p, sizeof...(Container)=%d, T的类型是:%s\n", this,
            sizeof...(Container), typeid(T).name());//boost库 type_id_with_cvr<>().pretty_name(); 
    }
};

int main() {
    myclass3_3<int, vector, list, deque> myc3;
    return 0;
}

dumpbin 会实例化5个类

  • myclass3<int>
  • myclass3<int, deque>
  • myclass3<int, list, deque>
  • myclass3<int,vector, list, deque>
  • myclass3_3<int, vector, list, deque>


    图片.png

    实际上就是递归,最后拆到没有参数。就会调用泛化版本

通过递归组合方式展开参数包

组合方式,是一种have 的模式,A 类种 有B 的实例。

/*
class B {

};

class A {

public:
    B b;
};

*/

老方式,定要特化跟着泛化的模式

template<typename ...Args>
class myclass {
    myclass() {
        printf("myclass() 泛化版本执行了,this = %p\n", this);
    }
};

template<typename First, typename... Others>
//class myclass<First, Others...> :private myclass<Others...> {
class myclass<First, Others...> {
public:
    myclass():m_i(0) {
        printf("myclass() 偏特化版本 is called , this = %p, sizeof...(Others) = %d\n", this, sizeof...(Others));
    }
    
    //myclass(First parf, Others... paro) :m_i(parf), myclass<Others...>(paro)
    myclass(First parf, Others... paro) :m_i(parf),m_o(paro...)//其实格式非常套路
    {
        cout << "------beigin------" << endl;
        cout << "sizeof...(Others): " << sizeof...(paro) << endl;
        printf("myclass::myclass(First parf, Others... paro)特化版本执行了, this = %p\n", this);
        cout << "m_i = " << m_i << endl;
        cout << "-------end--------" << endl;
    }
    
    First m_i;
    myclass<Others...> m_o;// 此处新增
};

template<>
class myclass<>
{
public:
    myclass() {
        printf("myclass() 0 个模板参数的泛化版本执行了,this = %p\n", this);
    }
};


int main() {
    myclass<int, float, double> my(12, 13.5, 23.0);
    return 0;
}

模式相对套路,去掉了 private 继承关系,去掉了 带参构造函数中对 class<Others...>的递归。
当然最后参数用光后,调用全空的 "全特化"

  • 不同于继承递归:
    this指针的不同


    图片.png

说明弄出来4个对象

从对象关系来看
继承:


继承展开.png

组合:


组合.png

通过tuple元组方式展开

何为tuple?实际上是一个可变参的类模板. c++17 方能引入

int main() {
    tuple<float, int, int> mytuple(12.5f,100, 24);
    
    cout << get<0>(mytuple) << endl;
    cout << get<1>(mytuple) << endl;
    cout << get<2>(mytuple) << endl;

    return 0;
}

基础模板:


template<typename... T>
void myfunc(const tuple<T...> & t) {
    
}

int main() {
    tuple<float, int, int> mytuple(12.5f,100, 24);
    
    cout << get<0>(mytuple) << endl;
    cout << get<1>(mytuple) << endl;
    cout << get<2>(mytuple) << endl;

    cout << "---------------------" << endl;

    myfunc(mytuple);

    return 0;
}

实际上借助了中间的tuple可变参类模板进行遍历

  • 扩展:
template<int count,int maxcount, typename... T>
class myclass{
public:
    static void myfunc(const tuple<T...>&t) {
        cout << "value= " << get<count>(t) << endl;
        myclass<count + 1, maxcount, T...>::myfunc(t);
    }
};

template<int maxcount, typename... T>
class myclass<maxcount, maxcount, T...> {
public:
    static void myfunc(const tuple<T...> &t) {

    }
};

template<typename... T>
void myTemplateFunc(tuple<T...> & t) {
    myclass<0, sizeof...(T), T...>::myfunc(t);
}

int main() {
    tuple<float, int, int>mytuple(12.6f, 100, 52);
    myTemplateFunc(mytuple);

    return 0;
}

tuple只做中间变量,tuple是现成的,要借助一个计数器,每处理一个参数,计数器+1,一直把所有参数处理完。当 count == maxcount时。
最后,提供一个模板偏特化,作为递归结束

基类参数包的展开

某个类的基类 也可以是可变参
即:可以有多个爹

//基类参数包的展开
template<typename... myClassPList>
class myclass : public myClassPList... {//存在一堆爹
public:
    myclass() : myClassPList()...
    {
        cout << "myclass : myclass 5, this = " << this << endl;
    }
};

class PA1
{
public:
    PA1()
    {
        cout << "PA1() is called, this= " << this << endl;
    }
private:
    char m_s1[100];
};

class PA2
{
public:
    PA2()
    {
        cout << "PA2() is called, this = " <<this<< endl;
    }
private:
    char m_s1[200];
};

class PA3
{
public:
    PA3()
    {
        cout << "PA3() is called, this = " << this <<endl;
    }
private:
    char m_s1[300];
};

int main() {
    myclass<PA1, PA2, PA3> obj;
    cout << "sizeof(obj): " << sizeof(obj) <<endl;
    return 0;
}
图片.png

可变模板的特化

没有全特化,只有特化

//泛化版本
template<typename... Args>
class myclass {
public:
    myclass()
    {
        printf("myclass 泛化 is called, this = %p, sizeof...(Args) = %d\n", this, sizeof...(Args));
    }
};

//偏特化版本
template<typename First, typename... Others>
class myclass<First, Others...> {
public:
    myclass() {
        printf("myclass<First, Others..>偏特化 is called, this =%p, sizeof...(Others) = %d\n", this, sizeof...(Others));
    }
};
//偏特化版本
template<typename Args>
class myclass <Args> {
public:
    myclass() {
        printf("myclass<Args> 偏特化 is called, this = %p\n", this);
    }
};
//偏特化版本
template<typename Arg1, typename Arg2>
class myclass<Arg1, Arg2>
{
public:
    myclass() {
        printf("myclass<Args1, Args2> 偏特化 is called, this = %p\n", this);
    }
};


int main() {
    myclass<int> myc1;
    myclass<int, float> myc2;
    myclass<int ,float, double> my3;
    myclass<int, float, double, char> my4;
    myclass<> my5;
    return 0;
}

感觉很像某些语言,比如 erlang 里的匹配模式:
执行结果:


图片.png

注意,class<> 走的是泛化,因为

template<typename... Args>
class myclass {
}

本身就包括 0 -> n 个参数的变化

相关文章

  • 模板与泛型 —— 可变参模板

    一、可变参函数模板 二、可变参类模板 C++ 11 中引入了 可变参模板 (Variadic Template):...

  • ★10.关于可变参数模板

    可变参数函数模板 可变参数类模板 可变参数函数模板的使用 转发参数包

  • 函数与数组

    函数的传参 可变参(不定参)arguments 参数的个数可变,参数数组 1.增加(改变数组内容) push() ...

  • C++11特性

    一、可变参数模板(Variadic Templates) 一、匿名函数(Lambda表达式) 二、可变模板参数 三...

  • 【C++ Templates(4)】可变参数模板

    可变参数模板示例 重载可变参数和非可变参数模板 上例也可以如下实现,如果两个函数模板只有尾置参数包不同,会优先匹配...

  • 可变参函数

    例如可变参函数printf的函数原型/函数声明为:int printf(char *fmt, ...);其中参数表...

  • C++ 泛型编程(一) —— 可变参数模板

    可变参数模板函数 可变参数模板是 C++ 11 中引入的一个新特性,它允许我们定义一个可以接受可变数目参数的模板函...

  • 04 可变模板参数

    可变参数模板示例 重载可变参数和非可变参数模板 前一例子也可以如下实现,如果两个函数模板只有尾置参数包不同,会优先...

  • template 模板

    两种通式:函数模板通式,类模板通式 一、函数模板通式 二、类模板通式 三种类型的模板形参:类型形参,非类型形参和模...

  • 函数 三

    无参装饰器模板 有参装饰器模板 叠加多个装饰器

网友评论

      本文标题:可变参模板

      本文链接:https://www.haomeiwen.com/subject/geouqrtx.html