顺序

顺序执行没有太多可说的,唯一需要注意的是,类型的声明必须按照顺序,否则编译器无法识别。不按照顺序的声明,在运行时的程序是合法的,编译过程会扫描两次,第一次是找声明位置,第二次是具体的转换。

分支

一般的分支选择

使用std::conditionalstd::conditional_t处理

#include <iostream>
#include <type_traits>struct T {};
struct F {};int main() {std::conditional_t<true, T, F>  t;std::conditional_t<false, T, F> f;return 0;
}

使用std::enable_ifstd::enable_if_t处理

#include <iostream>
#include <type_traits>int main() {std::enable_if<true, T> t;return 0;
}

使用模板的特化处理

#include <iostream>struct A;
struct B;template<typename T>
struct Fun_ {constexpr static int value = 0;
};template<>
struct Fun_<A> {constexpr static int value = 1;
};template<>
struct Fun_<B> {constexpr static int value = 2;
};int main() {std::cout << Fun_<int>::value << std::endl;std::cout << Fun_<A>::value << std::endl;std::cout << Fun_<B>::value << std::endl;return 0;
}
/*
输出:
0
1
2
*/

使用if constexpr处理

// C++17的表达式
if constexpr(/*...*/) {// 如果括号是true,则运行这一段,否则运行下一段
} else {}

简单实例,至少是C++17标准才可以

#include <iostream>
#include <string>template<bool Tag>
auto fun() {if constexpr (Tag) {return static_cast<int>(1);} else {return std::string("hello world !\n");}
}int main() {std::cout << fun<true>() << std::endl;std::cout << fun<false>() << std::endl;return 0;
}

输出:

1
hello world !

几个特殊点
不能在非完全特化的类模板中引入完全特化的分支代码!比如下面这一个错误的片段:

template<typename TF>
struct FOO {template<typename T>struct Fun_ {};template<>struct Fun_<int> {};
};

解决方式很简单,引入一个无用的默认void模板即可:

template<typename TF>
struct FOO {template<typename T, typename TDummy = void>struct Fun_ {};template<typename TDummy>struct Fun_<int, TDummy> {};
};

C++11的SFINAE特性,特性的全称是Substitution Failed Is Not An Error,匹配失败不是错误。如果出现多个匹配的情况,编译器会寻找最佳匹配方案,不存在找不到匹配的情况,下面一个实例:

#include <iostream>
#include <string>int fun(int a) {return a;
}std::string fun(const std::string& str) {return str;
}int main() {std::cout << fun("foo") << std::endl;std::cout << fun(1.1f) << std::endl;return 0;
}

代码的fun只匹配了std::stringint参数,有一个传入了float参数,根据SFINAE特性,这不是错误,而是主动匹配与之最接近的int类型。

上述的std::enable_if可以根据这个特性来构造:

#include <iostream>
#include <type_traits>template<bool CheckOut, typename T,std::enable_if_t<CheckOut>* = nullptr>
auto CheckOut_ (T && arg) {std::cout << "true, " << arg << std::endl;
}template <bool CheckOut, typename T,std::enable_if_t < !CheckOut > * = nullptr >
auto CheckOut_ (T&& arg) {std::cout << "false, " << arg << std::endl;
}int main() {CheckOut_<true, int>(5);CheckOut_<false, float>(1.1);return 0;
}

分支选择的短路逻辑

短路操作一般是指的运行期的计算短路操作,对于编译期间的元函数操作影响不大。先给出一个运行期短路操作的例子:

#include <iostream>bool fun() {std::cout << "fun()" << std::endl;return false;
}bool foo() {std::cout << "foo()" << std::endl;return true;
}int main() {int N;std::cin >> N;(N % 2 == 0) && fun() && foo();return 0;
}

输入奇数,则没有任何输出;输入偶数,则执行fun()函数;foo永远不会被执行。

然而编译期间不存在上述的短路行为,即&&不会阻止模板的展开。假设有下面的代码,其中meta1meta2假设都是元函数:

meta1<10> && meta2<100>;

则不论meta1<10>执行结果如何,meta2<100>都会递归展开执行。

编译期间的短路操作需要依靠模板的特化完成。给出一个例子,模板元函数AllOdd_<M, N>需要判断MN是否都是奇数,如果M不是奇数,那么N就不用判断了,直接短路操作即可。下面给出代码实例:

#include <iostream>
#include <cmath>template <size_t N>
struct IsOdd_ {constexpr static bool value = ((N % 2) == 1);
};template <bool cur, typename TNext>
constexpr static bool AndValue = false;template <typename TNext>
constexpr static bool AndValue<true, TNext> = TNext::value;template <size_t M, size_t N>
struct AllOdd_ {constexpr static bool is_M_odd = IsOdd_<M>::value;// 利用AndValue特化实现短路操作constexpr static bool value = AndValue<is_M_odd, IsOdd_<N>>;
};int main() {bool res1 = AllOdd_<2, 3>::value;  // 输出0bool res2 = AllOdd_<7, 9>::value;  // 输出1std::cout << res1 << std::endl;std::cout << res2 << std::endl;return 0;
}

循环

模板元编程的循环本质上是模板在编译期间的递归展开。编译器会保留递归展开的结果,从而为复用做优化,这是好的方面;坏的方面是,不是所用的重复结果都会被充分地利用,这样可能会出现组合爆炸的情况,导致内存占用过多,造成空间浪费。

一般的模板展开式循环

首先给出一个简单的实例,利用模板元求解1-N的累加和:

#include <iostream>// 泛型的模板
template <size_t N>
constexpr size_t Sum = N + Sum<N - 1>;// 模板特化,相当于递归终止的条件
template<>
constexpr size_t Sum<1> = 1;int main() {size_t s = Sum<10>;std::cout << s << std::endl;  // 输出55return 0;
}

上述代码中,没有出现循环结构,而是使用模板的递归展开进行计算,典型的函数式编程,不过是在编译期间进行计算的,这是模板元编写循环的典型使用方式。

再给出一个模板元容器的例子:

#include <iostream>// 模板特化,递归终止的条件是没有参数,没有模板就表示没有参数
template<size_t ...Inputs>
constexpr size_t Sum = 0;// 泛型模板,函数式编程处理累加和
template <size_t CurInput, size_t ...Inputs>
constexpr size_t Sum<CurInput, Inputs...> = \CurInput + Sum<Inputs...>;
int main() {size_t s = Sum<1, 2, 3, 4, 5>;std::cout << s << std::endl;   // 输出15return 0;
}

避免模板组合爆炸

一般来说,编译器会保留编译的结果,留作复用,从而提高编译速度。但是,复用的前提是模板的参数必须完全一致才可以!!否则无法复用,而且随着循环展开的,可能出现组合爆炸的情况,导致内存不足。

上述第一个累加和例子中,如果有:

size_t s1 = Sum<5>;
size_t s2 = Sum<10>;

那么,对于Sum<10>来说,1-5的累加和不用进行计算,而是直接使用Sum<5>的计算结果。但是,如果出现下面这种参数不匹配的情况,则无法进行重复利用,反而会存在大量的重复性计算。

给出一个区间累加和的模板元函数,计算M-N的累加值,闭区间的。

#include <iostream>template <int M>  // 起始值
struct Begin {template<int N, typename TDummy = void>struct Sum_ {   // 泛型累加模板constexpr static int value = N + Sum_ < N - 1 >::value;};template<typename TDummy>struct Sum_<M, TDummy> {  // 模板特化,终止值constexpr static int value = M;};template<int N>  // 输出的结果constexpr static int End = Sum_<N>::value;
};int main() {int s = Begin < -10 >::End<6>;int t = Begin < -11 >::End<6>;std::cout << s << std::endl;std::cout << t << std::endl;return 0;
}

上述模板中,虽然-10+…+5的过程和值是相同,但是由于参数不同,导致模板无法利用,所以产生模板空间浪费。正确的做法是拆分结构。

模板元实现顺序、分支和循环结构相关推荐

  1. python的两种循环结构_python分支和循环结构

    Python Python开发 Python语言 python分支和循环结构 1.分支结构 1.1应用场景 迄今为止,我们写的Python代码都是一条一条语句顺序执行,这种代码结构通常称之为顺序结构. ...

  2. 分支与循环结构测试题(有答案版)

    习题目录 python基础知识之分支与循环结构练习题 一.基础题: 分别用单分支结构和多分支结构实现判断一个年份是否是闰年. 写出判断⼀个数是否能够被2或者5整除,但是不能同时被2或者5整除的条件语句 ...

  3. j1_09_02。冒泡排序法。实现冒泡游戏功能关键算法。要求:综合使用分支、循环结构语句实现,直接输出结果不计分。

    package com; /*** 任务二:实现冒泡游戏功能关键算法并绘制流程图(30 分) 原始数组:a[]={1,9,3,7,4,2,5,0,6,8} 排序后: a[]={0,1,2,3,4,5, ...

  4. php多分支结构 案例,第4天 PHP分支、循环结构

    6. 流程控制 流程控制,就是研究程序的走向. 6.1. 流程控制概述 6.1.1. 三大流程结构: 顺序结构: 程序运行的自然状态,就是从前往后(从上到下)运行程序. 分支结构: 程序运行过程中,根 ...

  5. Python—语法基础(8) 分支、循环结构

    分支紧凑格式:<表达式1> if <条件> else <表达式2> # 分支结构 if <条件1> :<语句块1> elif <条件2 ...

  6. 分支和循环结构的应用(习题)

    目录 1.寻找水仙花数 2.将12345变成54321 3.百钱百鸡问题 4.CRAPS赌博游戏 5.斐波那契数列 6.打印100以内的素数 1.寻找水仙花数 说明:水仙花数也被称为超完全数字不变数. ...

  7. vue的分支及循环结构

    您可以在编辑器中c,v以下内容,即可查看(vue文件请自行到官网下载) <!DOCTYPE html> <html lang="en"><head&g ...

  8. 作业 3 应用分支与循环结构解决问题 统计字符个数

    /*统计字符,包括空格或回车,数字字符和其他字符*/#include<stdio.h> int main(void) {int digit,space,letter,other; /*定义 ...

  9. 作业 3 利用分支和循环结构解决问题

    1.输入一个整数x,计算分段函数. /*计算分段函数*/ #include<stdio.h> int main() {float x=0,y=0;printf("请输入x:&qu ...

最新文章

  1. EMC首席数据治理官:“受托人”是数据湖问责的关键
  2. Tor真的匿名和安全吗?——如果是http数据,则在出口节点容易被嗅探明文流量,这就是根本问题...
  3. Andriod --- JetPack (三):ViewModel 的诞生
  4. .NET CoreCLR开发人员指南(上)
  5. 服务器经常崩溃??让我们来看看简单的内存知识:C语言——内存管理
  6. Linux——CentOS建立一个最高权限的用户
  7. Ubuntu 下配置lamp环境
  8. 小程序开发合同_微信小程序怎么做?微信小程序制作流程? | 微信开放社区
  9. PSR-4——新鲜出炉的PHP规范
  10. python turtle库下载_win10+python3.8安装turtle库
  11. matlab 叉乘变点乘,向量点乘和叉乘
  12. jQuery仿百度商桥在线客服代码
  13. [IT名人堂]《人件》作者:汤姆.迪马可
  14. 亚马逊欧洲站的VAT需要多久申报一次
  15. [ACL2016]Pointing the Unknown Words
  16. 东南大学计算机学院保研比例,东南大学计算机科学与工程学院计算机技术(专业学位)保研...
  17. 怎么文字扫描识别?看完这篇你就会了
  18. 【科创人】慧安金科马宇翔:人生级决策总会选择有趣的选项,如今最在意平衡的生活...
  19. 下雨天为何会让人安心
  20. 华盛顿与李大学教授出版的这本数据结构书,是国外高等院校优秀教材

热门文章

  1. 计算机算法设计与分析 旅行售货员问题
  2. Iranian ChamPions Cup 水题
  3. C语言编程规范(排版)
  4. 深度学习——loss函数的学习笔记(legacy)
  5. 【已解决】能连接上无线,但打不开网页怎么办?
  6. 【已解决】显示屏没有问题,主机电源键按不开机怎么办?
  7. vijos:旅行家的预算[贪心]
  8. BigDecimal 计算余数
  9. 敏捷开发一千零一问系列之十一:需求谁做主?
  10. 一个效果很好的outlookbar控件CXTOutBarCtrl