莱州人社局网站,中企动力企业z云邮登陆,制作公司网页多少钱,wordpress通用页面模板点击蓝字关注我们泛型编程大家应该都很熟悉了#xff0c;主要就是利用模板实现“安全的宏”#xff0c;而模板元编程区别于我们所知道的泛型编程#xff0c;它是一种较为复杂的模板#xff0c;属于C的高阶操作了#xff0c;它最主要的优点就在于把计算过程提前到编译期主要就是利用模板实现“安全的宏”而模板元编程区别于我们所知道的泛型编程它是一种较为复杂的模板属于C的高阶操作了它最主要的优点就在于把计算过程提前到编译期能带来可观的性能提升。接下来一起来学习吧。1、概述模板元编程Template Meta programmingTMP是编写生成或操纵程序的程序也是一种复杂且功能强大的编程范式Programming Paradigm。C模板给C提供了元编程的能力但大部分用户对 C 模板的使用并不是很频繁大致限于泛型编程在一些系统级的代码尤其是对通用性、性能要求极高的基础库如 STL、Boost几乎不可避免在大量地使用 C 模板以及模板元编程。模版元编程完全不同于普通的运行期程序因为模版元程序的执行完全是在编译期并且模版元程序操纵的数据不能是运行时变量只能是编译期常量不可修改。另外它用到的语法元素也是相当有限不能使用运行期的一些语法比如if-else、for和while等语句都不能用。因此模版元编程需要很多技巧常常需要类型重定义、枚举常量、继承、模板偏特化等方法来配合因此模版元编程比较复杂也比较困难。2、模板元编程的作用C 模板最初是为实现泛型编程设计的但人们发现模板的能力远远不止于那些设计的功能。一个重要的理论结论就是C 模板是图灵完备的Turing-complete就是用 C 模板可以模拟图灵机。理论上说 C 模板可以执行任何计算任务但实际上因为模板是编译期计算其能力受到具体编译器实现的限制如递归嵌套深度C11 要求至少 1024C98 要求至少 17。C 模板元编程是“意外”功能而不是设计的功能这也是 C 模板元编程语法丑陋的根源。C 模板是图灵完备的这使得 C代码存在两层次其中执行编译计算的代码称为静态代码static code执行运行期计算的代码称为动态代码dynamic codeC的静态代码由模板实现编写C的静态代码就是进行C的模板元编程。具体来说 C 模板可以做以下事情编译期数值计算、类型计算、代码计算如循环展开其中数值计算实际意义不大而类型计算和代码计算可以使得代码更加通用更加易用性能更好也更难阅读更难调试有时也会有代码膨胀问题。编译期计算在编译过程中的位置请见下图。使用模板元编程的基本原则就是将负载由运行时转移到编译时同时保持原有的抽象层次。其中负载可以分为两类一类就是程序运行本身的开销一类则是程序员需要编写的代码。前者可以理解为编译时优化后者则是为提高代码复用度从而提高程序员的编程效率。图灵完备图灵完备是对计算能力的描述。简单判定图灵完备的方法就是看该语言能否模拟出图灵机图灵不完备的语言常见原因有循环或递归受限(无法写不终止的程序,如 while(true){}; ), 无法实现类似数组或列表这样的数据结构(不能模拟纸带). 这会使能写的程序有限图灵完备可能带来坏处, 如C的模板语言, 模板语言是在类型检查时执行, 如果编译器不加以检查,我们完全可以写出使得C编译器陷入死循环的程序.图灵不完备也不是没有意义, 有些场景我们需要限制语言本身. 如限制循环和递归, 可以保证该语言能写的程序一定是终止的。3、模板元编程的组成要素从编程范式上来说C模板元编程是函数式编程用递归形式实现循环结构的功能用C 模板的特例化提供了条件判断能力这两点使得其具有和普通语言一样通用的能力图灵完备性。模版元程序由元数据和元函数组成元数据就是元编程可以操作的数据即C编译器在编译期可以操作的数据。元数据不是运行期变量只能是编译期常量不能修改常见的元数据有enum枚举常量、静态常量、基本类型和自定义类型等。元函数是模板元编程中用于操作处理元数据的“构件”可以在编译期被“调用”因为它的功能和形式 和 运行时的函数类似而被称为元函数它是元编程中最重要的构件。元函数实际上表现为C的一个类、模板类或模板函数它的通常形式如下templateint N, int M
struct meta_func
{static const int value NM;
}调用元函数获取value值coutmeta_func1, 2::valueendl;meta_func的执行过程是在编译期完成的实际执行程序时是没有计算动作而是直接使用编译期的计算结果。元函数只处理元数据元数据是编译期常量和类型所以下面的代码是编译不过的int i 1, j 2;
meta_funci, j::value; //错误元函数无法处理运行时普通数据模板元编程产生的源程序是在编译期执行的程序因此它首先要遵循C和模板的语法但是它操作的对象不是运行时普通的变量因此不能使用运行时的C关键字如if、else、for可用的语法元素相当有限最常用的是enum、static const //用来定义编译期的整数常量
typedef/using //用于定义元数据[类型别名]
T/Args... //声明元数据类型【模版参数类型形参非类型形参】
Template //主要用于定义元函数【模版类特化偏特化】
:: //域运算符用于解析类型作用域获取计算结果元数据。【获取元数据元类型】实际上模板元中的if-else可以通过type_traits来实现它不仅仅可以在编译期做判断还可以做计算、查询、转换和选择。模板元中的for等逻辑可以通过递归、重载、和模板特化偏特化等方法实现。4、模板元编程的控制逻辑第一个 C 模板元程序由Erwin Unruh 在 1994 年编写这个程序计算小于给定数 N 的全部素数又叫质数程序并不运行都不能通过编译而是让编译器在错误信息中显示结果直观展现了是编译期计算结果C 模板元编程不是设计的功能更像是在戏弄编译器。从此C模板元编程的能力开始被人们认识到。在模版元程序的具体实现时由于其执行完全是在编译期所以不能使用运行期的一些语法比如if-else、for和while等语句都不能用。这些控制逻辑需要通过特殊的方法来实现。4.1、if判断模板元编程中实现条件if判断参考如下代码#include iostreamtemplatebool c, typename Then, typename Else class IF_ {}; //基础类模版templatetypename Then, typename Else
class IF_true, Then, Else { public: typedef Then reType; }; //类模版的偏特化; 如果第一个模版非类型参数为trueIF_true, Then, Else::reType的值为模版的第二个类型参数Thentemplatetypename Then, typename Else
class IF_false,Then, Else { public: typedef Else reType; }; //类模版的偏特化int main()
{const int len 4;// 定义一个指定字节数的类型typedefIF_sizeof(short)len, short,IF_sizeof(int)len, int,IF_sizeof(long)len, long,IF_sizeof(long long)len, long long,void::reType::reType::reType::reType int_my;std::cout sizeof(int_my) \n;
}/*分析最里面的一层* IF_sizeof(long long)len, long long, void::reType* 如果sizeof(long long) 4, 上面的表达式返回long long, 否则返回void
*/程序输出结果4。实际上从C11开始可以通过type_traits来实现。因为type_traits提供了编译期选择特性std::conditional它在编译期根据一个判断式选择两个类型中的一个和条件表达式的语义类似类似于一个三元表达式。它的原型是template bool B, class T, class F
struct conditional;所以上面的代码可以改写为如下代码#include iostream
#include type_traits
int main()
{const int len 4;// 定义一个指定字节数的类型typedefstd::conditionalsizeof(short)len, short,std::conditionalsizeof(int)len, int,std::conditionalsizeof(long)len, long,std::conditionalsizeof(long long)len, long long,void::type::type::type::type int_my;std::cout sizeof(int_my) \n;
}程序同样编译输出4。4.2、循环展开编译期的循环展开 Loop Unrolling可以通过模板特化来结束递归展开达到运行期的for和while语句的功能。下面看一个编译期数值计算的例子。#include iostream
templateint N class sum
{public: static const int ret sumN-1::ret N;
};
template class sum0
{public: static const int ret 0;
};
int main()
{std::cout sum5::ret std::endl;return 0;
}程序输出15。当编译器遇到sumt5时试图实例化之sumt5 引用了sumt5-1即sumt4试图实例化sumt4以此类推直到sumt0sumt0匹配模板特例sumt0::ret为 0sumt1::ret为sumt0::ret1为1以此类推sumt5::ret为15。值得一提的是虽然对用户来说程序只是输出了一个编译期常量sumt5::ret但在背后编译器其实至少处理了sumt0到sumt5共6个类型。从这个例子我们也可以窥探 C 模板元编程的函数式编程范型对比结构化求和程序for(i0,sum0; iN; i) sumi; 用逐步改变存储即变量 sum的方式来对计算过程进行编程模板元程序没有可变的存储都是编译期常量是不可变的变量要表达求和过程就要用很多个常量sumt0::retsumt1::ret...sumt5::ret。函数式编程看上去似乎效率低下因为它和数学接近而不是和硬件工作方式接近但有自己的优势描述问题更加简洁清晰没有可变的变量就没有数据依赖方便进行并行化。4.3、switch/case分支同样可以通过模板特化来模拟实现编译期的switch/case分支功能。参考如下代码#include iostream
using namespace std;
templateint v class Case
{
public:static inline void Run()
{cout default case endl;}
};
template class Case1
{
public:static inline void Run()
{cout case 1 endl;}
};
template class Case2
{
public:static inline void Run()
{cout case 2 endl;}
};
int main()
{Case2::Run();
}程序输出结果case 25、特性、策略与标签利用迭代器我们可以实现很多通用算法迭代器在容器与算法之间搭建了一座桥梁。求和函数模板如下#include iostream
#include vector
templatetypename iter
typename iter::value_type mysum(iter begin, iter end)
{typename iter::value_type sum(0);for(iter ibegin; i!end; i)sum *i;return sum;
}
int main()
{std::vectorint v;for(int i 0; i100; i)v.push_back(i);v.push_back(i);std::cout mysum(v.begin(), v.end()) \n;
}程序编译输出4950。我们想让 mysum() 对指针参数也能工作毕竟迭代器就是模拟指针但指针没有嵌套类型 value_type可以定义 mysum() 对指针类型的特例但更好的办法是在函数参数和 value_type 之间多加一层特性traits。templatetypename iter
class mytraits //标准容器通过这里获取容器元素的类型
{
public: typedef typename iter::value_type value_type;
};templatetypename T
class mytraitsT* //数组类型的容器通过这里获取数组元素的类型
{
public: typedef T value_type;
};templatetypename iter
typename mytraitsiter::value_type mysum(iter begin, iter end)
{typename mytraitsiter::value_type sum(0);for(iter ibegin; i!end; i)sum *i;return sum;
}
int main()
{int v[4] {1,2,3,4};std::cout mysum(v, v4) \n;return 0;
}程序输出10。其实C 标准定义了类似的 traits std::iterator_trait另一个经典例子是 std::numeric_limits 。traits特性对类型的信息如 value_type、 reference进行包装使得上层代码可以以统一的接口访问这些信息。C 模板元编程会涉及大量的类型计算很多时候要提取类型的信息typedef、 常量值等如果这些类型信息的访问方式不一致如上面的迭代器和指针我们将不得不定义特例这会导致大量重复代码的出现另一种代码膨胀而通过加一层特性可以很好的解决这一问题。另外特性不仅可以对类型的信息进行包装还可以提供更多信息当然因为加了一层也带来复杂性。特性是一种提供元信息的手段。策略policy一般是一个类模板典型的策略是 STL 容器的分配器如std::vector完整声明是templateclass T, class AllocallocatorT class vector;这个参数有默认参数即默认存储策略策略类将模板的经常变化的那一部分子功能块集中起来作为模板参数这样模板便可以更为通用这和特性的思想是类似的。标签tag一般是一个空类其作用是作为一个独一无二的类型名字用于标记一些东西典型的例子是 STL 迭代器的五种类型的名字。input_iterator_tag
output_iterator_tag
forward_iterator_tag
bidirectional_iterator_tag
random_access_iterator_tag实际上std::vectorint::iterator::iterator_category就是random_access_iterator_tag, 可以使用type_traits的特性is_same来判断类型是否相同。#include iostream
#include vector
#include type_traits
int main()
{
std::cout is_samestd::vectorint::iterator::iterator_category, std::random_access_iterator_tag ::value std::endl;
return 0;
}程序输出1。有了这样的判断还可以根据判断结果做更复杂的元编程逻辑如一个算法以迭代器为参数根据迭代器标签进行特例化以对某种迭代器特殊处理。标签还可以用来分辨函数重载。6、小结C模板元编程是图灵完备的且是函数式编程主要特点是代码在编译期执行可用于编译期数值计算能够获得更有效率的运行码。模板的使用也提高了代码泛化。与此同时模板元编程也存一定缺点主要有模板元编程产生的代码较为复杂难易阅读可读性较差大量模板的使用编译时容易导致代码膨胀提高了编译时间对于C来说由于各编译器的差异大量依赖模板元编程特别是最新形式的的代码可能会有移植性的问题。所以对于模板元编程我们需要扬其长避其短合理使用模板元编程。*声明本文于网络整理版权归原作者所有如来源信息有误或侵犯权益请联系我们删除或授权事宜。戳“阅读原文”我们一起进步