ch14-元编程

  1. 元编程的引入
  2. 顺序、分支、循环代码的编写方式
  3. 减少实例化的技巧

一.元编程的引入

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,一个类型
    - booltrue:选择下面的特化模板,里面定义了一个type(T相关)
    - boolfalse:匹配原始版本,里面没有定义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.其它技巧介绍

  • 减少分摊复杂度的数组元素访问操作

CPP-Note © 2024 | C++ 学习笔记

This site uses Just the Docs, a documentation theme for Jekyll.