返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++11中的变长模板的示例详解
  • 315
分享到

C++11中的变长模板的示例详解

C++11变长模板C++ 变长模板C++11 模板 2023-02-06 12:02:27 315人浏览 泡泡鱼
摘要

目录1.C99中的变长函数2.c++11中的变长函数3.详解变长模板3.1 更一般的SFINAE规则3.2 模板参数包的概念3.3 三个简单的例子3.4 函数参数包3.5 包扩展的进

1.C99中的变长函数

宏函数可以实现变长:就是采用C99中的变长宏__VA_ARGS__,如下所示为C99代码:

#include<stdio.h>
#define LOG(...){\
    fprintf(stderr,"%s: Line %d:\t",__FILE__,__LINE__);\
    fprintf(stderr,__VA_ARGS__);\
    fprintf(stderr,"\n");\
}
int main()
{
    int x=3;
    LOG("x=%d",x);//D:\study\c++\c++11\__VA_ARGS__.cpp: Line 10:    x=3
}

普通函数也可以实现变长

#include<stdio.h>
#include<stdarg.h>

​​​​​​​double SumOfFloat(int count,...)
{
    va_list ap;
    double sum=0;
    va_start(ap,count);//使得ap初始化为参数列表的handle
    for(int i=0;i<count;i++)
        sum+=va_arg(ap,double);//每次读取sizeof(double)的字节
    va_end(ap);
    return sum;
}
int main()
{
    printf("%f\n",SumOfFloat(3,1.2f,3.4,5.6));//10.200000
}

注意看上面代码中,SumOfFloat中的第一个参数count,这就意味著,你必须告诉函数它的参数的具体个数,这就不太方便,例如SumOfFloat(3,1.2f,3.4,5.6)中的第一个参数3。

2.C++11中的变长函数

C++11实现上面功能的一种方法是:使用initializer_list作为函数形参

#include<iOStream>
#include<initializer_list>
using namespace std;

double SumOfFloat(const initializer_list<double>& l)
{
    double sum=0;
    for(auto &val:l)
        sum+=val;
    return sum;
}
int main()
{
    printf("%f\n",SumOfFloat({1.2f,3.4,5.6}));//10.200000
}

这种思路感觉有点投机取巧,不太好

另一种方法是使用变长模板

#include<iostream>
template<typename... T>double SumOfFloat(T...);//模板函数声明

template<typename ...Args>//递归定义
double SumOfFloat(double a,Args... args)
{
    return a+SumOfFloat(args...);
}

double SumOfFloat(double a){return a;}//边界条件
int main()
{
    printf("%f\n",SumOfFloat(1.2f,3.4,5.6));//10.200000
}

先不对上述代码做解释,只要知道<typename... T>中的T叫做模板参数包,它是类型的,是一种特殊的类型,它可以被推导为多个类型的pack。即SumOfFloat(1.2f,3.4,5.6)会将T推导为double,double,double的一种pack类型。

除此之外,必须知道这种设计必须是递归定义的。

3.详解变长模板

首先讲一个前置概念:SFINAE.

3.1 更一般的SFINAE规则

在C++98中,我们就有SFINAE法则:Substitution failure is not an error

即匹配失败不算失败,在C++中就是,即使模板展开失败,也不会报错

struct Test
{
    typedef int FOO;
};
template<typename T>
void foo(typename T::FOO){}

template<typename T>
void foo(T){}

int main()
{
    foo<Test>(10);
    foo<int>(10);
}

上面代码在C++98中也可以通过编译,上面foo(typename T::FOO)中typename显式的表示,T::FOO是一个类型,在foo<int>(10);中,编译器会尝试用 第一个模板函数来匹配它,但是会发现int::FOO错误,但是编译器不会报错,这就是SFINAE

在C++11中对SFINAE规则做了放松,

template<int I>
struct A {};
char xxx(int);
char xxx(float);
template<typename T>A<sizeof(xxx((T)0))> f(T){}
int main()
{
    f(1);
}

有一些C++98编译器会对上式报错,这是因为它们认为模板参数过于复杂,即这里的sizeof(xxx((T)0)),不过现在的编译器都狠优秀,它们可以完成这种表达式的推导,C++11的标准是:只要表达式中没有出现外部于表达式本身的元素,编译器都可以完成推导

3.2 模板参数包的概念

接下来的内容会有一定难度。

我们知道,在C++98中模板参数有3种:类型的,非类型的,模板类型的。

template<typename T,int i,template<typename> class A>中T就是类型的,i是非类型的,A是模板类型的。

在C++11中,我们为了支持变长的模板,我们加入一种新的模板参数:模板参数包。

所以在C++11中模板参数有4种:类型的,非类型的,模板类型的和模板参数包。

而模板参数包又可以细分为3种:类型的模板参数包,非类型的模板参数包,模板类型的模板参数包。

模板参数包是一种pack,下面我们从模板推导角度来解释这种pack:

1.(类型的)模板参数包

template <typename T1,typename T2>class B{};
template <typename... A>class Template: private B<A...>{};
Template<X,Y> xy;

上面中,<typename...A>中A是 (类型的)模板参数包,它可以接收任意多个类型参数作为模板参数,具体来说,Template<X,Y>会将A推导为X和Y类型的pack。

B<A...>中A...是一种包扩展,它是模板参数包unpack的结果。由于A被推导为X和Y的pack,所以A...就被具体解释为X,Y,然后具体化为B<X,Y>。

如果我们使用Template<X,Y,Z> xyz就会引发推导错误,没有任何一个模板适配,这是因为此时A...被解释为3个类型:X,Y,Z,它无法和B匹配。

2.(非类型的)模板参数包

template<int i,long j,unsigned int k>class B{};
template<int ...A> struct Pack: private B<A...>{};
Pack<1,0,2> data; 

<int ... A>中A是 (非类型的)模板参数包,它可以接收分离多个非类型参数作为模板参数,具体来说,Pack<1,0,2>会将A推导为整值1,0,2的pack,而B<A...>中A...是一种包扩展,由于A推导为整值1,0,2的pack,所以A...被具体解释为1,0,2,然后具体化为B<1,0,2>

3.(模板类型的)模板参数包

template <typename T> class A;
template <typename T> class B;
template<template<typename> class T1,template<typename> class T2> class C{};
template<template<typename> class ...T> struct Pack:private C<T...>{};
Pack<A,B> data;

<template<typename> class ...T>中T是 (模板类型的)模板参数包,它可以接收多个模板作为模板参数,具体来说,Pack<A,B>会将T推导为A和B的pack,而C<T...>中T...就是一种包扩展,由于T推导为A和B的pack,所以T...就被具体解释为A,B,然后具体化为C<A,B>

3.3 三个简单的例子

变长模板必须采用递归设计,下面是3个简单的例子,请仔细阅读。

1.(类型的)模板参数包的使用

下面给出C++11中tuple的简单实现,

template<typename... Elements> class tuple;//模板声明

template <typename Head,typename... Tail>//递归定义
class tuple<Head,Tail...>:private tuple<Tail...>
{
    Head head;
};
template<> class tuple<>{};//边界条件

同样也是递归定义,这种递归的设计就是变长模板最晦涩的地方。

当实例化tuple<double,int,char,float>类时,

第一次:Head被推导为double,Tail...被推导为int,char,float

第二次:Head被推导为int,Tail...被推导为char,float

第三次:Head被推导为char,Tail...被推导为float

第三次:Head被推导为float,Tail...被推导为空

最后由class tuple<>进行递归构造出模板

2.(非类型的)模板参数包的使用

#include<iostream>
using namespace std;

template<long... nums> struct Multiply;//模板声明

template<long first,long... last>//递归定义
struct Multiply<first,last...>
{
    static const long val=first*Multiply<last...>::val;
};
template <>//边界条件
struct Multiply<>
{
    static const long val=1;
};
int main()
{
    cout<<Multiply<2,3,4,5>::val<<endl;
    cout<<Multiply<22,44,66,88,9>::val<<endl;
}

上面这种编程方式,叫做模板元编程,他将乘法的计算放到模板推导过程中,就是把计算过程放在编译阶段,这样运行时就不需要计算了

3.(模板类型的)模板参数包的使用

template <typename T> class Module1{};
template <typename T> class Module2{};

template<typename I,template<typename>class ... B>struct Container;//模板声明
template<typename I,template<typename> class A,template<typename> class... B>
struct Container<I,A,B...>//递归定义
{
    A<I> a;
    Container<I,B...> b;
};
template<typename I> struct Container<I>{};//边界条件

int main()
{
    Container<int,Module1,Module2> a;
}

3.4 函数参数包

函数参数包是在写变长模板函数中的一个概念

函数参数包也是一种pack型变量,它也存在unpack,包扩展的概念。

void g(int,char,double);
template<typename ... T> 
void f(T... args)
{
    g(args...);
}
f(1,'c',1.2);

在<typename ... T>中的T是 (类型的)模板参数包 。

在f(T... args)中T...叫做 包扩展

在f(T... args)中的args是一种类型为T...的变量,它叫函数参数包

在g(args...)中的args...也是一种包扩展,它是将argsunpack后的产物

例如,这里f(1,'c',1.2)就会将T推导为int,char,double的pack,于是T...就被具体解释为int,char,double,然后args就是类型为T...的一种变量,args的值是1,'c',1.2的pack,则我们可以在f中调用g(args...)完成对args的unpack。

下面看一个,C++11中提案的prinf()函数的实现

#include<iostream>
#include<stdexcept>
using namespace std;

void Printf(const char*s)//边界条件
{
    while(*s)
    {
        if(*s=='%' && *++s!='%')//确保`%%`不出现
            throw runtime_error("invalid fORMat string: missing arguments");
        cout<<*s++;
    }
}
template<typename T,typename ...Args>//递归定义
void Printf(const char*s,T value,Args... args)
{
    while(*s)
    {
        if(*s=='%' && *++s!='%')//确保`%%`不出现
        {
            cout<<value;
            return Printf(++s,args...);
        }
        cout<<*s++;
    }
    throw runtime_error("extra arguments provided to Printf");
}
int main()
{
    Printf("hello %s\n",(string)"world");
}

变长模板的难点在于我们不知道参数的个数,我们必须采用递归定义,就像上面的Printf就是递归定义的,采用的是数学归纳法,可以细细品味一下上面那段代码。

3.5 包扩展的进阶

...符号可以放在意想不到的地方,例如:

template<typename... A> class T:private B<A>...{};//#1
template<typename... A> class T:private B<A...>{};//#2

对于实例化T<X,Y>,#1会被解释为

class T<X,Y> class T:private B<X>,private B<Y>{};

#2会被解释为

class T<X,Y> class T:private B<X,Y>{};

看一下下面这些例子:

#include<iostream>
using namespace std;

template<typename... T>
void DummyWrapper(T... t){};

template<typename T>
T pr(T t){
    cout<<t;
    return t;
}
template<typename... A>
void VTPrint(A... a)
{
    DummyWrapper(pr(a)...);
}
int main()
{
    VTPrint(1,", ",1.2,", abc\n");
}

上面这段代码,某些编译器(例如g++)的结果是逆序的:

, abc 
1.2, 1

应该是,不同的编译器可能包扩展的顺序不太一样,有些的逆序的。

下面我们看一段晦涩难懂的代码

#include<iostream>
#include<tuple>
using namespace std;

template<typename A,typename B> 
struct S
{
    int a=1;
};

template<
    template<typename...> class T, typename... TArgs,
    template<typename...> class U, typename... UArgs
    >
    struct S< T<TArgs...> , U<UArgs...> >{int a=2;};

int main()
{
    S<int,float> p;
    S<tuple<int,char>,tuple<float>> s;
    //S<tuple,int,char,tuple,float> s;编译出错 
    cout<<s.a<<endl;//2
}

注意上面这段代码中,最终输出是2,奇怪的地方在于,S<tuple<int,char>,tuple<float>>如何匹配第二个模板呢?这种设计是约定俗称的,没有任何原因,记住上面这种巧妙的设计就行了。

3.6 sizeof...()的使用

sizeof...()其实狠简单的,它就是获得pack中的变量的个数,有些用的

#include<cassert>
#include<iostream>
using namespace std;

template<typename... A>
void Print(A... arg)
{
    assert(false);
}

void Print(int a1,int a2,int a3,int a4,int a5,int a6)
{
    cout<<a1<<", "<<a2<<", "<<a3<<", "<<a4<<", "<<a5<<", "<<a6<<endl;
}

template<class... A>int Vaargs(A... args)
{
    int size=sizeof...(args);//或者sizeof...(A)

    switch (size)
    {
        case 0:Print(99,99,99,99,99,99);
                break;
        case 1:Print(99,99,args...,99,99,99);
                break;
        case 2:Print(99,99,args...,99,99);
                break;
        case 3:Print(args...,99,99,99);
                break;
        case 4:Print(99,args...,99);
            break;
        case 5:Print(99,args...);
            break;
        case 6:Print(args...);
            break;
        default:
            Print(0,0,0,0,0,0);
    }
}
int main()
{
    Vaargs();//99, 99, 99, 99, 99, 99
    Vaargs(1);//99, 99, 1, 99, 99, 99 
    Vaargs(1,2);//99, 99, 1, 2, 99, 99
    Vaargs(1,2,3);//1, 2, 3, 99, 99, 99  
    Vaargs(1,2,3,4);//99, 1, 2, 3, 4, 99   
    Vaargs(1,2,3,4,5);//99, 1, 2, 3, 4, 5 
    Vaargs(1,2,3,4,5,6);//1, 2, 3, 4, 5, 6  
    Vaargs(1,2,3,4,5,6,7);//0, 0, 0, 0, 0, 0 
}

3.7 变长模板和完美转发的配合

#include<iostream>
using namespace std;

struct A
{
    A(){};
    A(const A&a){cout<<"Copy Constructed "<<__func__<<endl;}
    A(A&& a){cout<<"Move Constructed "<<__func__<<endl;}
};

struct B
{
    B(){};
    B(const B&b){cout<<"Copy Constructed "<<__func__<<endl;}
    B(B&& b){cout<<"Move Constructed "<<__func__<<endl;}
};

template<typename... T> struct  MultiTypes;//模板声明

template<typename T1,typename... T>//递归定义
struct MultiTypes<T1,T...>: public MultiTypes<T...>
{
    T1 t1;
    MultiTypes<T1,T...>(T1 a,T... b):t1(a),MultiTypes<T...>(b...)
    {
        cout<<"MultiTypes<T1,T...>(T1 a,T... b)"<<endl;
    }
};

template<> struct MultiTypes<>//边界条件
{
    MultiTypes<>(){cout<<"MultiTypes<>()"<<endl;}
};

template<template<typename...> class VariadicType,typename... Args>
VariadicType<Args...> Build(Args&& ... args)
{
    return VariadicType<Args...>(std::forward<Args>(args)...);
}

int main()
{
    A a;
    B b;
    Build<MultiTypes>(a,b);
    //等价于Build<MultiTypes,A,B>(a,b);
}

MultiTypes<>()
MultiTypes<T1,T...>(T1 a,T... b)
MultiTypes<T1,T...>(T1 a,T... b)

没啥好说的,这就是完美转发,它根部就不调用移动构造和拷贝构造函数,完全都是靠引用传递的

以上就是C++11中的变长模板的示例详解的详细内容,更多关于C++11变长模板的资料请关注编程网其它相关文章!

--结束END--

本文标题: C++11中的变长模板的示例详解

本文链接: https://lsjlt.com/news/194302.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C++11中的变长模板的示例详解
    目录1.C99中的变长函数2.C++11中的变长函数3.详解变长模板3.1 更一般的SFINAE规则3.2 模板参数包的概念3.3 三个简单的例子3.4 函数参数包3.5 包扩展的进...
    99+
    2023-02-06
    C++11变长模板 C++ 变长模板 C++11 模板
  • C++11新特性之变长参数模板详解
    目录C++11 变长参数模板变长函数参数包如何解参数包sizeof()获得函数参数个数递归模板函数变参模板展开结论C++11 变长参数模板 在C++11之前,无论是类模板 还是函数...
    99+
    2024-04-02
  • 详解C++11中模板的优化问题
    1. 模板的右尖括号 在泛型编程中,模板实例化有一个非常繁琐的地方,那就是连续的两个右尖括号(>>)会被编译器解析成右移操作符,而不是模板参数表的结束。我们先来看一段关...
    99+
    2024-04-02
  • C++模板index_sequence使用示例详解
    目录引言integer_sequenceindex_sequencemake_index_sequence使用场景index_sequence_for结语引言 integer_se...
    99+
    2022-12-08
    C++模板index_sequence C++ 模板
  • C++11中的可变参数模板/lambda表达式
    目录1.可变参数模板递归函数方式展开参数包逗号表达式展开参数包2.lambda表达式先来看看lambda表达式的例子:lambda表达式语法1.可变参数模板 C++11的新特性可变参...
    99+
    2023-03-24
    C++11 lambda表达式 C++11 可变参数模板
  • C++11可变参数模板的参数转发举例分析
    本篇内容主要讲解“C++11可变参数模板的参数转发举例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C++11可变参数模板的参数转发举例分析”吧!实例很多软件系统都存在日志(log)功能,通...
    99+
    2023-06-19
  • C++11模板函数的默认模板参数举例分析
    这篇文章主要介绍“C++11模板函数的默认模板参数举例分析”,在日常操作中,相信很多人在C++11模板函数的默认模板参数举例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C++11模板函数的默认模板参数举...
    99+
    2023-06-19
  • C++11中模板隐式实例化与显式实例化的定义详解分析
    目录1. 隐式实例化2. 显式实例化声明与定义3. 显式实例化的用途1. 隐式实例化 在代码中实际使用模板类构造对象或者调用模板函数时,编译器会根据调用者传给模板的实参进行模板类型推...
    99+
    2024-04-02
  • C#中的composite模式示例详解
    目录写在前面一个简单例子基层员工类经理类公司架构类客户端代码再想一下使用组合模式进行重构透明型安全型重构后的代码(透明型)写在前面 Composite组合模式属于设计模式中比较热门的...
    99+
    2024-04-02
  • c++可变参数模板使用示例源码解析
    目录前言认识可变模板参数使用可变模板参数递归法特例化包拓展完美转发总结前言 我们知道,C++模板能力很强大,比起Java泛型这种语法糖来说,简直就是降维打击。而其中,可变参数模板,...
    99+
    2023-01-13
    c++可变参数模板 c++可变参数
  • C++常用的11种设计模式解释及示例代码详解
    目录工厂模式单例模式适配器模式外观模式代理模式桥接模式模板方法模式策略模式观察者模式责任链模式c++常用的设计模式包括单例模式、工厂模式、抽象工厂模式、适配器模式、装饰者模式、代理模...
    99+
    2023-02-07
    C++常用的11种设计模式 C++常用设计模式
  • MySQL中的长事务示例详解
    前言: 『入门MySQL』系列文章已经完结,今后我的文章还是会以MySQL为主,主要记录下近期工作及学习遇到的场景或者自己的感悟想法,可能后续的文章不是那么连贯,但还是希望大家多多支持。言归正传,本篇文章主...
    99+
    2024-04-02
  • C++模板基础之函数模板与类模板实例详解
    目录泛型编程 函数模板 函数模板的概念 函数模板的格式 函数模板的原理 函数模板的实例化 函数模板的匹配原则 类模板 类模板的定义格式 类模板的实例化 总结泛型编程  ...
    99+
    2024-04-02
  • C++模板重载的示例分析
    本篇文章为大家展示了C++模板重载的示例分析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。1.重载模板函数模板可以使得同一个函数对不同类型使用,非常地方便。但有的时候类型不同,只是通过模板是没办法解...
    99+
    2023-06-22
  • C++模板编程的示例分析
    这篇文章主要为大家展示了“C++模板编程的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“C++模板编程的示例分析”这篇文章吧。模板初阶泛型编程在计算机程序设计领域,为了避免因数据类型的不...
    99+
    2023-06-25
  • C++ 类模板与成员函数模板示例解析
    目录类模板类模板与成员函数模板的区别类模板 前面以函数模板为例,介绍了具体化与实例化。那么对于类模板,有什么不同呢? 类包括成员变量和成员函数,他们都可以包含类模板的模板参数。而成...
    99+
    2023-01-03
    C++ 类模板成员函数模板 C++ 类模板成员函数模板
  • C++11 中的override详解
    目录1 公有继承1.1 纯虚函数 (pure virtual)1.2 普通虚函数1.2.1 方法一1.2.2 方法二1.3 非虚函数2 重写 (override)小结:参考资料1 公...
    99+
    2024-04-02
  • C++11中的可变参数模板和lambda表达式怎么使用
    本篇内容介绍了“C++11中的可变参数模板和lambda表达式怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1.可变参数模板C++1...
    99+
    2023-07-05
  • C++11中线程锁和条件变量的示例分析
    这篇文章主要介绍了C++11中线程锁和条件变量的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。线程std::thread类, 位于<thread>头文件,...
    99+
    2023-06-15
  • 详解C++11中的线程锁和条件变量
    目录线程锁条件变量小结线程 std::thread类, 位于<thread>头文件,实现了线程操作。std::thread可以和普通函数和 lambda 表达式搭配使用。...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作