ch14-元编程
- 元编程的引入
- 顺序、分支、循环代码的编写方式
- 减少实例化的技巧
一.元编程的引入
1.1.从泛型编程到元编程
- 泛型编程 —— 使用一套代码处理不同类型
- 对于一些特殊的类型需要引入额外的处理逻辑 —— 引入操纵程序的程序
- ==元编程与编译期计算==
1.2.第一个元程序示例( Erwin Unruh 1994)
- 在编译错误中产生质数:所有的计算过程都是在编译期完成
1.3.使用编译期运算辅助运行期计算
- 不是简单地将整个运算一分为二
- 详细分析哪些内容可以放到编译期,哪些需要放到运行期(是一个设计问题,设计出哪些可以放到编译期进行计算:比如读取配置文件,配置文件是用户定义的,大部分是运行期执行的)
- 如果某种信息需要在运行期确定,那么通常无法利用编译期计算
1.4.元程序的形式
-
模板, constexpr 函数,其它编译期可使用的函数(如 sizeof )
#include<iostream> template<int x>//模板 struct M{ constexpr static int val = x+1; }; int main(){ return M<3>::val;//4,编译期完成计算 }#include<iostream> //constexpr 函数 constexpr int fun(int x){ consteval int fun(int x){//效果相同 return x + 1; } constexpr int val = fun(3); int main(){ return val;//4,编译期完成计算 }#include<iostream> //sizeof constexpr int fun(int x){ consteval int fun(int x){//效果相同 return x + 1; } constexpr int val = fun(3); int main(){ return sizeof(int);//4,编译期完成计算 } -
通常以函数为单位,也被称为函数式编程
1.5.元数据
- 基本元数据:数值、类型、模板
#include<iostream> template<int x>//模板 struct M{ //输入是数值输出是数值 constexpr static int val = x+1; }; constexpr int val = fun(3); int main(){ return sizeof(int);//输入是类型输出是数值 }#include<iostream> #include<type_traits> template<int x>//模板 struct M{ using type = int[x]; }; int main(){ M<3>type a;//输入为数值输出为类型 std::cout<<std::is_same_v<decltype(a), int[3]<<std::endl;//1, int[3]是编译期完成的计算 } - 数组
#include<iostream> #include<type_traits> template<int x>//模板 struct M{ using type = int[x]; }; int main(){ int a[3];//运行计数? }#include<iostream> #include<type_traits> template<unsigned... T> struct Cont;//类模板声明 template<auto... T> struct Cont2;//类模板声明 template<typename... T> struct Cont3;//类模板声明 template<template<typename>class... T> struct Cont4;//模板数组 int main(){ int a[3];//运行计数? }
1.6.元程序的性质
-
输入输出均为 “ 常量 ”
#include<iostream> #include<type_traits> constexpr int x = 3;//和常量打交道 int main(){ int x; x = 10; x = x + 1;// } -
函数无副作用
#include<iostream> #include<type_traits> int fun(int input){ static int x = 0;//有变量,有副作用,不能子啊编译期计算 return input + (x++); } int main(){ }
1.7.type_traits元编程库
-
C++11 引入到标准中,用于元编程的基本组件
-
type_traits里面函数基本就是元函数
-
元编程需要大量使用type_traits库
二.顺序、分支、循环代码的编写方式
2.1.顺序代码的编写方式
-
类型转换示例:为输入类型去掉引用并添加 const
- 代码无需至于函数中
-
通常置于模板中,以头文件的形式提供
#include<iostream> #include<type_traits> template<typename T> struct Fun{ //给定输入,把引用去掉 using RemRef = typename std::remove_reference<T>::type; using type = typename std::add_const<RemRef>::type; }; int main(){ Fun<int&>::type x = 3;//const int x = 3; } - 更复杂的示例:
- 以数值、类型、模板作为输入
-
以数值、类型、模板作为输出
#include<iostream> #include<type_traits> //类型和数值作为输入,输出数值 template<typename T, unsigned S> struct Fun{ //给定输入,把引用去掉 using RemRef = typename std::remove_reference<T>::type; constexpr static bool value = (sizeof(T) == S); }; int main(){ constexpr bool res = Fun<int&, 4>::value;//这就是一个函数调用 std::cout<< res <<"\n";// 1 }#include<iostream> #include<type_traits> //类型和数值作为输入,输出数值 template<typename T, int S> struct Fun{ //给定输入,把引用去掉 using RemRef = typename std::remove_reference<T>::type; constexpr static int value = (sizeof(T) - S); }; int main(){ std::cout<<Fun<double&, 4>::value<<"\n";// 4,都是在编译期完成的 }
-
引入限定符防止误用
#include<iostream> #include<type_traits> //类型和数值作为输入,输出数值 template<typename T, int S> struct Fun{ private: using RemRef = typename std::remove_reference<T>::type; public: constexpr static int value = (sizeof(T) - S); }; int main(){ Fun<int&, 4>::RemRef x;//不加private是可以编译的 std::cout<<Fun<double&, 4>::value<<"\n";// 4,都是在编译期完成的 } -
通过别名模板简化调用方式
#include<iostream> #include<type_traits> //类型和数值作为输入,输出数值 template<typename T, int S> struct Fun_{ private: using RemRef = typename std::remove_reference<T>::type; public: constexpr static int value = (sizeof(T) - S); }; template<typename T,int S>//别名模板 constexpr auto Fun = Fun_<T,S>value; int main(){ std::cout<<Fun<double&, 4>::value<<"\n";// 4,都是在编译期完成的 }
2.2.分支代码的编写方式
```c
#include<iostream>
#include<type_traits>
constexpr int fun(int x){
if(x>3){
return x*2;
}else{
return x - 100;
}
}
//这种if else也是编译期计算,但是可以使用的场景很小.
//只能在constexpr这种函数里面使用,一般函数就不能work
constexpr int x = fun(100);
int main(){
}
```
-
基于 if constexpr 的分支:便于理解只能处理数值,同时要小心引入运行期计算
#include<iostream> #include<type_traits> constexpr int fun(int x){ if(x>3){ return x*2; }else{ return x - 100; } } constexpr int x = fun(100); template<int x>//参数是编译期获得 int fun(){//运行期执行 if constexpr(x>3){//编译期分支 return x*2; }else{ return x - 100; } } int main(){ int y = fun<100>();//运行期调用 } -
基于(偏)特化引入分支:常见分支引入方式但书写麻烦
#include<iostream> #include<type_traits> template<int x> struct Imp { constexpr static int value = x*2; }; tempalte<>//模板特化 struct Imp<100> { constexpr static int value = 100 - 3; }; constexpr int x = Imp<97>::value;//194 constexpr int x = Imp<100>::value;//97 int main(){ }#include<iostream> #include<type_traits> template<int x> struct Imp { constexpr static int value = x*2; using type = int; }; tempalte<>//模板特化 struct Imp<100> { constexpr static int value = 100 - 3; using type = double; }; using x = Imp<50>::type; // x=int using x = Imp<100>::type; // x=double int main(){ }#include<iostream> #include<type_traits> template<int x> struct Imp;//声明 template<int x> requires (x<100) struct Imp<x>//特化 { constexpr static int value = x*2; using type = int; }; tempalte<>//模板特化 requires (x>=100) struct Imp<x>//特化 { constexpr static int value = x - 3; using type = double; }; constexpr auto x = Imp<50>::type; // 100 constexpr auto x = Imp<100>::value; //97 int main(){ } -
基于 std::conditional 引入分支:语法简单但应用场景受限–>只适用于类型
- 可能实现
template<bool B, class T, class F>
struct conditional { typedef T type; };
template<class T, class F>
struct conditional<false, T, F> { typedef F type; };
第一个参数为bool型,如果为true则返回第二个类型,如果为false则返回第三个类型,有点像三目运算符
```c
#include <iostream>
#include <type_traits>
#include <typeinfo>
int main()
{
typedef std::conditional<true, int, double>::type Type1;
typedef std::conditional<false, int, double>::type Type2;
typedef std::conditional<sizeof(int) >= sizeof(double), int, double>::type Type3;
std::cout << typeid(Type1).name() << '\n';
std::cout << typeid(Type2).name() << '\n';
std::cout << typeid(Type3).name() << '\n';
}
```
- 基于 ==SFINAE(替换失败并非错误)== 引入分支
-
基于 std::enable_if 引入分支:语法不易懂但功能强大
- 可能实现 template<bool B, class T = void> struct enable_if {}; template<class T>//特化版本 struct enable_if<true, T> { typedef T type; }; - 传入一个bool值,一个类型 - bool为true时:选择下面的特化模板,里面定义了一个type(与T相关) - bool为false时:匹配原始版本,里面没有定义typename template< bool B, class T = void > using enable_if_t = typename enable_if<B,T>::type; - enable_if_t则直接获取type - 如果前面参数为false时:enable_if_t无效,没有定义type - 如果前面参数为true,则直接返回type函数模板enable_if使用例子:
#include<iostream> #include<type_traits> //函数模板实现-->也可以使用类模板实现 template<int x, std::enablle_if_t<(x<100) >* = nullptr> constexpr auto fun() { return x*2; } template<int x, std::enablle_if_t<(x>=100) >* = nullptr> constexpr auto fun() { return x - 3; } constexpr auto x = Imp<100>(); //97 int main(){ }类模板enable_if使用例子:
#include<iostream> #include<type_traits> template<int x, typename = void*> struct Imp;//声明 template<int x> struct Imp<x, std::enablle_if_t<(x>=100) >* >//特化 { constexpr static int value = x*2; using type = int; }; tempalte<>//模板特化 requires (x>=100) struct Imp<x, std::enablle_if_t<(x>=100) >*>//特化 { constexpr static int value = x - 3; using type = double; }; constexpr auto x = Imp<101>::value; // 98 constexpr auto x = Imp<99>::value; //198 int main(){ }-
注意用做缺省模板实参不能引入分支!
#include<iostream> #include<type_traits> template<int x, typename = std::enablle_if_t<(x<100) > > constexpr auto fun() { return x*2; } template<int x, typename = std::enablle_if_t<(x>=100) > > constexpr auto fun() { return x - 3; } constexpr auto x = fun<100>(); //err:函数重定义 int main(){ }
-
-
基于 std::void_t 引入分支: C++17 中的新方法,通过 “ 无效语句 ” 触发分支–>可变长度的别名模板
查看cpp reference
- 基于 concept 引入分支: C++20 中的方法
- 可用于替换 enable_if
-
基于三元运算符引入分支: std::conditional 的数值版本
#include<iostream> #include<type_traits> template<int x> constexpr auto fun = (x<100) ? x*2 : x-3; constexpr auto x = fun<102>(); int main(){ std::cout<<x<<std::endl;//99 }
2.3.循环代码的编写方式
-
简单示例:计算二进制中包含 1 的个数
- 循环代码 #include<iostream> #include<type_traits> //递归实现循环 //1100011b //1+fun<(110001b)> //1+1+fun<(11000b)> //1+1+0+fun<(1100b)> //1+1+0+0+fun<(110b)> //1+1+0+0+0+fun<(11b)> //1+1+0+0+0+1+fun<(1b)> //1+1+0+0+0+1+1+fun<(0b)> //1+1+0+0+0+1+1+0 template<int x> constexpr auto fun = (x%2) + fun<x/2>;//循环体 template<>//特化,实现递归结束 constexpr auto fun<0> = 0; constexpr auto x = fun<99>; int main(){ std::cout<<x<<std::endl;//4 } -
使用递归实现循环
-
任何一种分支代码的编写方式都对应相应的循环代码编写方式
-
使用循环处理数组:获取数组中 id=0,2,4,6… 的元素
- 循环代码 #include<iostream> #include<type_traits> template <typename...> class Cont;//数组 using Input = Cont<int,char,double,bool,void>; //Imp包含了两个模板形参 //Res:要返回的值,或返回值的一部分 //Rem:待处理的数据 template <typename Res, typename Rem>//模板 struct Imp;//声明 //模板特化 //processed:处理结果 //typename T1, typename T2, typename... TRsmain:待处理数据,大于等于两个类型 template<typename... processed, typename T1, typename T2, typename... TRsmain> struct Imp<Cont<processed...>, Cont<T1,T2,TRemain...>> { using type1 = Cont<processed..., T1>; //把T1拿出来,T2丢掉 using type = typename Imp<type1, cont<TRemain...>>::type; } //特化:递归结束:待处理只剩一个元素 template<typename... processed, typename T1> struct Imp<Cont<processed...>, Cont<T1>> { using type = Cont<processed..., T1>; } //特化:递归结束:待处理只剩零个元素 template<typename... processed> struct Imp<Cont<processed...>, Cont<>> { using type = Cont<processed...>; } using Output = Imp<Cont<>,Input>::type; int main(){ std::cout<<std::is_same_v<Output, Cont<int,double,void> <<std::endl;//1 } -
相对复杂的示例:获取数组中最后三个元素
- 循环代码 #include<iostream> #include<type_traits> template <typename...> class Cont;//数组 using Input = Cont<int,char,double,bool,void>; //Imp包含了两个模板形参 //Res:要返回的值,或返回值的一部分 //Rem:待处理的数据 template <typename Res, typename Rem>//模板 struct Imp;//声明 //特化1 //已经处理部分包含三个U1.U2.U3, //待处理部分不为空 template<typename U1,typename U2,typename U3, typename T, typename... TRsmain> struct Imp<Cont<U1,U2,U3>, Cont<T,TRemain...>> { using type1 = Cont<U2,U3,T>; using type = typename Imp<type1, cont<TRemain...>>::type; } //特化2 //已经处理部分包含两个U1.U2, //待处理部分不为空 template<typename U1,typename U2, typename T, typename... TRsmain> struct Imp<Cont<U1,U2>, Cont<T,TRemain...>> { using type1 = Cont<U1,U2,T>; using type = typename Imp<type1, cont<TRemain...>>::type; } //特化3 //已经处理部分包含一个U1 //待处理部分不为空 template<typename U1, typename T, typename... TRsmain> struct Imp<Cont<U1>, Cont<T,TRemain...>> { using type1 = Cont<U1,T>; using type = typename Imp<type1, cont<TRemain...>>::type; } //特化4 //已经处理部分为空 //待处理部分不为空 template<typename T, typename... TRsmain> struct Imp<Cont<>, Cont<T,TRemain...>> { using type1 = Cont<T>; using type = typename Imp<type1, cont<TRemain...>>::type; } //特化5 //已经处理部分为多个? //待处理部分为空 template<typename... TProcessed> struct Imp<Cont<TProcessed...>, Cont<> > { using type = Cont<TProcessed...>; } //调用:已经处理的为空,Input为待处理的部分,数组-->不为空 //首先匹配特化4-->把Input的第一个元素放入Cont:int //匹配特化3-->把Input的第二个元素放入Cont:int,char //匹配特化2-->把Input的第三个元素放入Cont:int,char,double //匹配特化1-->把U1丢掉,把Input的第四个元素放入Cont:char,double,bool //匹配特化1-->把U1丢掉,把Input的第五个元素放入Cont:double,bool,void-->此时待处理为空 //匹配特化5-->待处理为空:系统返回double,bool,void using Output = Imp<Cont<>,Input>::type; int main(){ std::cout<<std::is_same_v<Output, Cont<double,bool,void> <<std::endl;//1 }优化:
- 循环代码 #include<iostream> #include<type_traits> template <typename...> class Cont;//数组 using Input = Cont<int,char,double,bool,void>; //Imp包含了两个模板形参 //Res:要返回的值,或返回值的一部分 //Rem:待处理的数据 template <typename Res, typename Rem>//模板 struct Imp//优化处 { using type = Res; }; //特化1 //已经处理部分包含三个U1.U2.U3, //待处理部分不为空 template<typename U1,typename U2,typename U3, typename T, typename... TRsmain> struct Imp<Cont<U1,U2,U3>, Cont<T,TRemain...>> { using type1 = Cont<U2,U3,T>; using type = typename Imp<type1, cont<TRemain...>>::type; } //特化2 //已经处理部分包含两个U1.U2, //待处理部分不为空 template<typename U1,typename U2, typename T, typename... TRsmain> struct Imp<Cont<U1,U2>, Cont<T,TRemain...>> { using type1 = Cont<U1,U2,T>; using type = typename Imp<type1, cont<TRemain...>>::type; } //特化3 //已经处理部分包含一个U1 //待处理部分不为空 template<typename U1, typename T, typename... TRsmain> struct Imp<Cont<U1>, Cont<T,TRemain...>> { using type1 = Cont<U1,T>; using type = typename Imp<type1, cont<TRemain...>>::type; } //特化4 //已经处理部分为空 //待处理部分不为空 template<typename T, typename... TRsmain> struct Imp<Cont<>, Cont<T,TRemain...>> { using type1 = Cont<T>; using type = typename Imp<type1, cont<TRemain...>>::type; } //调用:已经处理的为空,Input为待处理的部分,数组-->不为空 //首先匹配特化4-->把Input的第一个元素放入Cont:int //匹配特化3-->把Input的第二个元素放入Cont:int,char //匹配特化2-->把Input的第三个元素放入Cont:int,char,double //匹配特化1-->把U1丢掉,把Input的第四个元素放入Cont:char,double,bool //匹配特化1-->把U1丢掉,把Input的第五个元素放入Cont:double,bool,void-->此时待处理为空 //匹配基础版本-->待处理为空:系统返回(Res)double,bool,void using Output = Imp<Cont<>,Input>::type; int main(){ std::cout<<std::is_same_v<Output, Cont<double,bool,void> <<std::endl;//1 }
三.减少实例化的技巧
3.1.减少实例化技巧
- 为什么要减少实例化
- 提升编译速度,减少编译所需内存
#include<iostream> #include<vector> int mian(){ //这两行代码的处理代价是不一样的,第二行更少 std::vector<int> a; std::vector<int> b; return 0; }#include<iostream> #include<vector> #include<type_traits> template<size_t A> struct Wrap_ {//类模板 //嵌套模板不允许完全特化 //引入假的部分特化实现完全特化:TDummy //这是一个循环:计算0+...+ID template <size_t ID, typename TDummy = void> struct imp { coonstexpr static size_t value = ID + imp<ID-1>::value; }; template <typename TDummy>//特化:递归结束 struct imp<0,TDummy> { constexpr static size_t value = 0; }; template <size_t ID> constexpr static size_t value = imp<A + ID>::value; }; int mian(){ //5+4+3+2+1+0=15 //构造的实例: //Wrap_<3>::imp<5>,Wrap_<3>::imp<4>,...,Wrap_<3>::imp<0>, std::cout<<Wrap_<3>::value<2> <<std::endl; //12+...+0=18 //构造的实例: //Wrap_<10>::imp<12>,Wrap_<11>::imp<4>,...,Wrap_<10>::imp<0>, std::cout<<Wrap_<10>::value<2> <<std::endl; }
3.2.相关技巧
-
提取重复逻辑以减少实例个数
#include<iostream> #include<vector> #include<type_traits> //放外面:减少实例构造 //嵌套模板不允许完全特化 //引入假的部分特化实现完全特化:TDummy //这是一个循环:计算0+...+ID template <size_t ID, typename TDummy = void> struct imp { coonstexpr static size_t value = ID + imp<ID-1>::value; }; template <typename TDummy>//特化:递归结束 struct imp<0,TDummy> { constexpr static size_t value = 0; }; template<size_t A> struct Wrap_ {//类模板 template <size_t ID> constexpr static size_t value = imp<A + ID>::value; }; int mian(){ //5+4+3+2+1+0=15 //构造的实例: //imp<5>,imp<4>,...,imp<0>, std::cout<<Wrap_<3>::value<2> <<std::endl; //12+...+0=18 //构造的实例:-->会重复使用前面构造的 //imp<12>,imp<4>,...,imp<0>, std::cout<<Wrap_<10>::value<2> <<std::endl; } -
conditional 使用时避免实例化
#include<iostream> #include<vector> #include<type_traits> using Res = std::conditional_t<false, std::remove_reference_t<int&>, std::remove_reference_t<double&> >; //等价于 std::conditional<false, std::remove_reference_t<int&>, std::remove_reference_t<double&> >::type; //等价于 std::conditional<false, int, double>::type; //这里Res就会引入更多的实例化 //std::remove_reference<int&>::type-->会对typen进行实例化 //因为第一个参数是false,其实第二个参数不需要进行实例化 int mian(){ }解决:
#include<iostream> #include<vector> #include<type_traits> using Res = std::conditional_t<false, std::remove_reference<int&>, std::remove_reference<double&> >::type; //当false时:直接返回第三个参数的type,不会对第二个参数进行实例化 int mian(){ std::cout<< std::is_same_v<Res,double> >>;//1 } -
使用 std::conjunction / std::disjunction 引入短路逻辑
3.3.其它技巧介绍
- 减少分摊复杂度的数组元素访问操作