C++ template —— template metaprogram(九)
metaprogramming含有“对一个程序进行编程”的意思。换句话说,编程系统将会执行我们所写的代码,来生成新的代码,而这些新代码才真正实现了我们所期望的功能。通常而言,metaprogramming这个概念意味着一种反射的特性:metaprogramminig组件只是程序的一部分,而且它也只生成一部分代码或者程序。
使用metaprogramming的目的是为了实现更多的功能,并且是花费的开销(代码大小,维护的开销等来衡量)更小。另一方面,metaprogramming的最大特点在于:某些用户自定义的计算可以在程序翻译期进行。而这通常都能够在性能或接口简单性方面带来好处;甚至为两方面同时带来好处。
本篇讲解的metaprogramming概念要依赖于前面关于trait和类型函数的讨论。
17.1 metaprogram的第一个实例(递归模板)
模板实例化机制是一种基本的递归语言机制,可以用于在编译期执行复杂的计算。因此,这种随着模板实例化所出现的编译期计算通常就被称为template metaprogramming。
看一个简单的例子:如何在编译期计算3的幂
//meta/pow3.hpp
#ifndef POW3_HPP
#define POW3_HPP // 用于计算3的N次方的基本模板
template <int N>
class Pow3
{
public:
enum { result = * Pow3<N->::result };
}; // 用于结束递归的全局特化
template<>
class Pow3<>
{
public:
enum { return = };
}; #endif // POW3_HPP
在这里,Pow3<>模板(包含它的特化)就被称为一个template metaprogramming。它描述一些可以在翻译期(编译期)进行求值的计算,而这整个求值过程属于模板实例化过程的一部分。
17.2 枚举值和静态常量
在原来的C++编译器中,在类声明的内部,枚举值是声明“真常值”(也称为常量表达式)的唯一方法。然而,现在C++的标准化过程引入了在类内部进行静态常量初始化的概念。我们可以作如下修改上面的例子:
//meta/pow3b.hpp
#ifndef POW3_HPP
#define POW3_HPP // 用于计算3的N次方的基本模板
template <int N>
class Pow3
{
public:
static int const result = * Pow3<N->::result;
}; // 用于结束递归的全局特化
template<>
class Pow3<>
{
public:
static int const result = ;
}; #endif // POW3_HPP
新的例子中我们使用静态常量成员而不是枚举值。然而,该版本存在一个缺点:静态成员变量只能是左值。因此,如果你具有一个如下的声明:
void foo(int const&);
而且你把上一个metaprogram的结果传递进去,即:
foo(Pow3<>::result);
那么编译器将必须传递Pow3<7>::result的地址,而这会强制编译器实例化静态成员的定义,并为该定义 分配内存。于是,该计算将不再局限于完全的“编译期”效果。然而,枚举值却不是左值(也就是说,它们并没有地址)。因此,当你通过引用传递枚举值的时候,并不会使用任何静态内存,就像是以文字常量的形式传递这个完成计算的值一样。所以,下面的所有例子,我们使用枚举值而不是静态常量。
17.3 第二个例子:计算平方根
// meta/sqrt1.hpp #ifndef SQRT_HPP
#define SQRT_HPP // 用于计算sqrt(N)的基本模板
template <int N, int LO = , int HI = N>
class Sqrt
{
// 计算中点
enum { mid = (LO + HI +) / }; // 借助二分查找一个较小的result
enum { return = (N<mid*mid) ? Sqrt<N, LO, mid->::result : Sqrt<N, mid, HI>::result };
}; // 局部特化,适用于LO等于HI
template<int N, int M>
class Sqrt<N, M, M>
{
public:
enum { result = M };
}; #endif // SQRT_HPP
现在考虑当编译器试图计算下面表达式的时候:
(<=*) ? Sqrt<, , >::result : Sqrt<, , >::result
这时候,编译器会实例化"?:"运算符两边的模板,这会产生数量庞大的实例化体,总数大约是N的两倍。这并不是我们所期望的,因为对于大多数编译器而言,模板实例化通常都会是一个代价高昂的过程,特别对于内存开销而言。所以我们放弃使用"?:"运算符,而是使用我们在前面xxxxx博文讲解过的IfThenElse模板:
// meta/sqrt2.hpp #include "ifthenelse.hpp" // 用于主要递归步骤的基本模板
template <int N, int LO = , int HI = N>
class Sqrt
{
// 计算中点
enum { mid = (LO + HI +) / }; // 借助二分查找一个较小的result
typedef typename IfThenElse<(N<mid*mid), Sqrt<N, LO, mid->, Sqrt<N, mid, HI> >::ResultT SubT; enum { result = SubT::result };
}; // 局部特化,适用于LO等于HI
template<int N, int S>
class Sqrt<N, S, S>
{
public:
enum { result = S };
};
可以把IfThenElse看成一个简易装置(实际上是模板),它能根据给定布尔常量的值,在两个类型中选择出其中一个。记住:为一个类模板实例定义一个typedef并不会导致C++编译器实例化该实例的实体。
17.4 使用归纳变量
详见书籍,不作笔记
17.5 计算完整性
Pow3<>和Sqrt这两个例子说明:一个template metaprogram可以包含下面几部分:
(1)状态变量:也就是模板参数。
(2)迭代构造:通过递归
(3)路径选择:通过使用条件表达式或者特化
(4)整型(即枚举里面的值应该为整型)算法
模板实例化通常都要消耗巨大的编译器资源,而且扩展的递归实例化也会很快地降低编译器的效率,甚至耗光所有的可用资源。
C++标准建议最多只进行17层的递归实例化,但实际开发中又很容易就超过这个限制。然而,在某些情况下,metaprogram又是实现高效率模板的一个不可替代的工具。
17.6 递归实例化和递归模板实参
书中在本节向我们介绍了一个例子,表明当使用递归模板实参的时候,编译器为每个类型保存一个mangled name将会变得非常大。故而,在其他条件都相同的情况下,在组织递归实例化的时候,我们仍然(趋向于)避免在模板实参中使用递归嵌套的实例化。
17.7 使用metaprogram来展开循环 这是本篇博文一个实用的应用程序,用于展开数值计算的循环:
// meta/loop1.hpp #ifndef LOOP1.HPP
#define LOOP1.HPP template <typename T>
inline T dot_product(int dim, T* a, T* b)
{
T result = T();
for (int i = ; i < dim; ++i)
{
result += a[i]*b[i];
}
return result;
} #endif // LOOP1.HPP
上面程序的问题在于:对于许多迭代,编译器通常都会优化这种循环(即迭代),而在这个例子中,这种优化却会带来反面的效果(why?)。
如果实用了旨在执行千万次点乘计算的程序库组件,那么差别可能就会很大了。template metaprogramming为我们解决了这个问题。程序修改如下:
// meta/loop2.hpp #ifndef LOOP2.HPP
#define LOOP2.HPP // 基本模板
template <int DIM, typename T>
class DotProduct
{
public:
static T result (T* a, T* b){
return *a * *b + DotProduct<DIM-, T>::result(a+, b+);
}
}; // 作为结束条件的局部特化:一元vector的情况
template <typename T>
class DotProduct<, T>
{
public:
static T result (T* a, T* b){
return *a * *b;
}
}; // 辅助函数
template <int DIM, typename T>
inline T dot_product(T* a, T* b)
{
return DotProduct<DIM, T>::result(a, b);
} #endif // LOOP2.HPP
可以如下调用:
dot_product<>(a, b);
这个表达式将实例化一个辅助函数模板,而在此函数模板内部将会直接调用:
DotProduct<, int>::result(a, b); // 模板实参分别是:非类型模板参数,通过函数模板实参演绎得到的模板参数
C++ template —— template metaprogram(九)的更多相关文章
- 在struts2中整合ajax时出现Template /template/ajax/head.ftl not found错误时的处理方法
Struts2 Ajax出现错误“Template /template/ajax/head.ftl not found” 2013-02-08 18:26:27| 分类: 默认分类|字号 订阅 ...
- Template template parameter(模板參数) example
/********************************************************************************* Copyright (C), 19 ...
- c++11-17 模板核心知识(十二)—— 模板的模板参数 Template Template Parameters
概念 举例 模板的模板参数的参数匹配 Template Template Argument Matching 解决办法一 解决办法二 概念 一个模板的参数是模板类型. 举例 在c++11-17 模板核 ...
- template template parameter
#include <iostream> using namespace std; template<typename T> class A { }; template<t ...
- org.thymeleaf.exceptions.TemplateInputException: Error resolving template "/ template might not exist or might not be accessible by any of the configured
异常现象:在本地打包部署完全没有问题,资源文件也都可以映射上,但是打包成jar包部署到服务器上时,就一直报异常,异常信息如下: 严重: Servlet.service() for servlet [d ...
- 报错Error resolving template template might not exist or might not be accessible解决方案
"C:\Program Files\Java\jdk1.8.0_144\bin\java" "-javaagent:D:\IntelliJ IDEA Community ...
- c++11-17 模板核心知识(十四)—— 解析模板之依赖型模板名称(.template/->template/::template)
tokenization与parsing 解析模板之类型的依赖名称 Dependent Names of Templates Example One Example Two Example Three ...
- thymeleaf在开发环境正常,但用jar运行时报错 Error resolving template template might not exist or might not be accessible
解决方案: (1)配置中添加 spring.thymeleaf.prefix=classpath:/templates (2)指向模板的路径 不加 /
- template.process(root, out)的用法(shiro项目中来的九)
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "utf-8" ...
随机推荐
- 【转】【MySQL】Mysql模糊查询like提速优化
在使用msyql进行模糊查询的时候,很自然的会用到like语句,通常情况下,在数据量小的时候,不容易看出查询的效率,但在数据量达到百万级,千万级的时候,查询的效率就很容易显现出来.这个时候查询的效率就 ...
- (笔记)Mysql命令select from:查询表中的数据(记录)
select from命令用来查询表中的数据. 1) 查询所有行命令格式: select <字段1, 字段2, ...> from < 表名 > where < 表达式 ...
- Android R.java:10: “duplicate class”
在使用mmm命令编译android的app时出现错误error : R.java:10: "duplicate class" 参考方法: http://www.xuebuyuan. ...
- 2016年第七届蓝桥杯C/C++B组省赛题目解析
题目1:煤球数目 有一堆煤球,堆成三角棱锥形.具体:第一层放1个,第二层3个(排列成三角形),第三层6个(排列成三角形),第四层10个(排列成三角形),....如果一共有100层,共有多少个煤球?请填 ...
- Hessian示例:Java和C#通信
一个简单的利用Hessian在Java和C#之间通信的例子,服务端为Java,客户端为C#. 资源下载 先要准备好C#和Java的第三方类库:http://hessian.caucho.com/ Hs ...
- Spring和mybatis的整合
一.搭建项目开发环境 1. 新建一个maven项目SpringMybatis,项目结构如下: 说明: src/main/java 存放java代码和映射文件: com.study.springmyba ...
- Java如何暂停线程一段时间?
在Java编程中,如何暂停线程一段时间? 以下示例显示如何通过创建sleepThread()方法来暂停线程一段时间. package com.yiibai; public class Suspendi ...
- Ubuntu 14.04 安装 DevStack与遇到的的问题记录
本文总结Ubuntu 14.04下部署DevStack的过程以及一些可能遇到的问题. 一.安装 以下的操作最好在普通用户下进行,至少在git clone devstack的时候使用普通用户,这样可以避 ...
- WAS集群:记一次Node Agent不活动问题解决过程
之前很少接触集群,准确地说是很少接触项目现场的实施工作,或者说接触到的都是比较简单的实施工作,安装Linux.WAS.Oracle相对来说都比较简单.一直埋头干着研发的活,干着不要紧,一干就是好几年. ...
- LAMP架构介绍MySQL、MariaDB介绍 MySQL安装