C++学习笔记——元编程

元编程

元编程的定义

定义:
元编程(Metaprogramming)是指某类计算机程序的编写,这类计算机程序编写或者操纵其他程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作。(百度上的定义)

简单来说就是能在编译器处理一些程序,或进行一些运算就算元编程。

形式:

  1. 模板
template <int x>
struct M
{
    constexpr static int bval=x+1;
};
int main()
{
    return M<5>::val;  //编译器得到val=4
}
  1. constexpr函数
constexpr int fun(int x)
{
    return x+1;
}
constexpr int val=fun(3);  //编译器调用函数得到val=4
  1. sizeof
sizeof(int)  //4

C++11引入元编程库,在#include<type_traits>中

例如:

  • is_void
  • is_array
  • is_integral
  • is_const
  • remove_const
  • remove_pointer
  • add_pointer

元编程的编写

介绍三种元程序代码的编写方法。

顺序代码

#include<type_traits>
template <typename T>
struct Fun
{
    using RmRef=typename std::remove_reference<T>::type;  //去引用
    using type=typename std::add_const<T>::type;  //去const
};
int main
{
    Fun<int&>::type x=3;
}
template <typename T,unsigned S>
struct Fun
{
    using RemRef=typename std::remove_reference<T>::type;
    constexpr static bool value=(sizeof(RemRef)==S);
};

template <typename T,unsigned S>
using Fun_v=Fun<T,S>::value;
int main()
{
    constexpr bool res=Fun<T,S>::value;
    std::cout<<Fun_v<int,4><<std::endl;  //类似is_same_v
}

分支代码

  1. 基于 if constexpr
constexpr int fun(int x)
{
    if(x>2)
    {
        return x*2;
    }
    else
    {
        return x-100;
    }
}
int main()
{
    constexpr int x=fun(100);
}

//或用数值模板参数
template <int x>
int fun()
{
    if constexpr(x>2)   //必须有constexpr 否则运行期才进行分支运算
    {
        return x*2;
    }
    else
    {
        return x-100;
    }
}
  1. 基于特化
template <int x>
struct Imp
{
    constexpr static int value=x*2;
};

template <>
struct Imp<100>
{
    constexpr static int value=100-3;
};
  1. 基于requires引入约束

有的编译器不支持(clang支持,须引入C++2a)

template <int x>
struct Imp;

template <int x>
    requires(x<100)
struct Imp <x>
{
  ...
  ...
};

template <int x>
    requires(x>=100)
struct Imp <x>
{
  ...
  ...
};

int main()
{
    constexpr auto x=Imp<100>::value;
}
  1. 基于std::conditional
//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;
};
template<bool B,class T,class F>
using conditional_t=conditional<B,T,F>::type;
int main()
{
    using type1=std::conditional<true,int,double>::type//ture 时返回第一个模板类型,flase时返回第二个模板类型
}

应用场景受限,只能用于类型,有点像三元表达式
5. 基于SFINAE引入分支
SFINAE(匹配失败不是错误)

利用enable_if

可能实现:

template<bool B,class T=void>
struct enable_if{};
template <class T>
struct enable_if<true,T>
{
    typedef T type;
};
template<bool B,class T=void>
    using enable_if_t=enable_if<B,T>::type;

exanple1:

template<int x,std::enable_if_t<(x<100)>*=nullptr>
constexpr auto fun()
{
    return x*2;
}
template<int x,std::enable_if_t<(x>=100)>*=nullptr>
constexpr auto fun()
{
    return x-3;     //匹配失败并非错误,当enable_if为false时不返回类型,不会报错,只是不匹配
}
int main()
{
    constexpr auto x=fun<99>();
}

example2:

template<int x,typename=void>
struct Imp;

template<int x>
struct Imp<x,std::enable_if_t<(x<100)>
{
    constexpr static int value=x*2;
};
template<int x>
struct Imp<x,std::enable_if_t<(x>=100)>
{
    constexpr static int value=x-3;
};
int main()
{
    constexpr auto x=Imp<99>::value;
}

注意:不可这样编写,会导致函数重定义,模板函数不支持偏特化。

template<int x,std::enable_if_t<(x<100)>
constexpr auto fun()
{
    return x*2;
}
template<int x,std::enable_if_t<(x>=100)>
constexpr auto fun()
{
    return x-3;
}

利用void_t

(c++17引入)

可能实现:

template <class...>
using void_t=void;

example(代码来自cppreference):

template <typename T,typename=void>
struct is_iterable:std::false_type{}; //false_type包含一个成员value为false
template <typename T>
struct is_iterable<T,std::void_t<decltype(std::declval<T>().begin()),
                                 decltype(std::declval<T>().end())>>
                                 :std::true_type{}; //true_type包含一个成员value为true
int main()
{
    std::cout<<is_iterable<std::vector<double>>::value<<std::endl;//判断输入类型是否有begin和end迭代器
}

无begin和end迭代器时默认模板形参是void,调用第一个类模板(第二个有错)

有begin和end迭代器时默认模板形参是void,调用第二个类模板(第二个正确,完美匹配)。相当于除非void_t的条件不满足,否则一定调用第二个类模板。

  1. 基于concept引入分支

用concept替换enable_if即可
7. 基于三元运算符引入分支

template <int x>
constexpr auto fun=(x<100)? x*2:x-3;
constexpr auto x=fun<99>;

循环代码

一般基于递归来实现。

example1:获取一个数的二进制表示中1的个数

template <int x>
constexpr auto fun=(x%2)+fun<x/2>;

template <>
constexpr auto fun<0>=0;   //递归停止

int main()
{
    constexpr auto x=fun<99>
}

example2: 获取编译期数组中索引为奇数的所有元素

template <typename...>class Cont;
using input=Cont<int,char,double,bool,void>;
template <typename Res,typename Rem>
struct Imp;

template <typename... Processed,typename T1,typename T2,typename... TRemain>
struct Imp<Cont<processed...>,Cont<T1,T2,TRemain>>
{
    using type1=Cont<Processed... T1>;
    using type=typename Imp<type1,Cont<TRemain...>>::type; //typename 为防止歧义而引入
};
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...>;        
    //由于输入数组可能为奇数也可能为偶数,故递归结束有种情况,
    //分别为剩一个元素和一个元素也不剩,分开讨论两种情况设置递归结束程序。
};
int main()
{
    using output=Imp<Cont<>,input>::type;
}

example3:打印编译期数组

template <unsigned... T>
class Cont;  //编译期数组
template <typename T>
struct print
{
};

template <unsigned T1,unsigned... T>
struct print<Cont<T1,T...>>
{
    inline static void fun()
    {
        std::cout<<T1;
        print<Cont<T...>>::fun();
    }   
};

template <>
struct print<Cont<>>
{
    inline static void fun()
    {
    }  
};

int main()
{
    using input=Cont<1,2,3>;
    print<input>::fun();
}

example4:翻转编译器数组

template <unsigned... T>
class Cont;
template<typename T1,typename T2>
struct reverse;

template<unsigned... Process,unsigned T1,unsigned... Remain>
struct reverse<Cont<Process...>,Cont<T1,Remain...>>
{
    using type1=Cont<T1,Process...>;
    using type=typename reverse<type1,Cont<Remain...>>::type;
};

template<unsigned... Process>
struct reverse<Cont<Process...>,Cont<>>
{ 
    using type=Cont<Process...>;
};

int main()
{
    using input=Cont<1,2,3>;
    using type1=reverse<Cont<>,input>::type;
}

example5:编译器长整数数组相加,例如Cont<1,2,3>+Cont<2,3>=Cont<1,4,6>,使用了example3和4

整个程序流程为,先翻转数组,之后对每一位进行相加,得到最终结果输出。

template <unsigned T,typename=void>
struct Add_Res;

template <unsigned T>
struct Add_Res<T,std::enable_if_t<(T<10)>>
{
    constexpr static unsigned Carbit=0;
    constexpr static unsigned Res=T;
};

template <unsigned T>
struct Add_Res<T,std::enable_if_t<(T>=10)>>
{
    constexpr static unsigned Carbit=1;
    constexpr static unsigned Res=T-10;
};

/*****************************************************************************************
 * normal condition, the result of the bit is a+b+carry bit 
 *****************************************************************************************/
template<typename result,typename Carbit,typename T1,typename T2>
struct Add;

template<unsigned... result,unsigned Carbit,unsigned T1,unsigned... Fnum,unsigned T2,unsigned... Snum>
struct Add<Cont<result...>,Cont<Carbit>,Cont<T1,Fnum...>,Cont<T2,Snum...>>
{
    constexpr static unsigned Nvalue=Add_Res<T1+T2+Carbit>::Res;
    constexpr static unsigned Ncarbit=Add_Res<T1+T2+Carbit>::Carbit;
    using last_type=typename Add<Cont<Nvalue,result...>,Cont<Ncarbit>,Cont<Fnum...>,Cont<Snum...>>::last_type;
};

/*****************************************************************************************
 * b is null,the result of the bit is a+carry bit 
 *****************************************************************************************/
template<unsigned... result,unsigned Carbit,unsigned T1,unsigned... Fnum>
struct Add<Cont<result...>,Cont<Carbit>,Cont<T1,Fnum...>,Cont<>>
{
    constexpr static unsigned Nvalue=Add_Res<T1+Carbit>::Res;
    constexpr static unsigned Ncarbit=Add_Res<T1+Carbit>::Carbit;
    using last_type=typename Add<Cont<Nvalue,result...>,Cont<Ncarbit>,Cont<Fnum...>,Cont<>>::last_type;
};

/*****************************************************************************************
 * a is null,the result of the bit is b+carry bit
 *****************************************************************************************/
template<unsigned... result,unsigned Carbit,unsigned T2,unsigned... Snum>
struct Add<Cont<result...>,Cont<Carbit>,Cont<>,Cont<T2,Snum...>>
{
    constexpr static unsigned Nvalue=Add_Res<T2+Carbit>::Res;
    constexpr static unsigned Ncarbit=Add_Res<T2+Carbit>::Carbit;
    using last_type=typename Add<Cont<Nvalue,result...>,Cont<Ncarbit>,Cont<>,Cont<Snum...>>::last_type;
};

/*****************************************************************************************
 * both a and b is null, stop the function iteration, return the result.
 *****************************************************************************************/
template<typename T1,typename T2>
struct LastResult;
template<unsigned... result,unsigned Carbit>
struct Add<Cont<result...>,Cont<Carbit>,Cont<>,Cont<>>
{
    using last_type=typename LastResult<Cont<Carbit>,Cont<result...>>::result;
};

/*****************************************************************************************
 * if the carry_bit is 1 at last,we should put 1 at the begin of the result to get the last result.
 * For example 1+999=carry_bit:1 ,result_= 000  ->1000
 *****************************************************************************************/
template<unsigned car_bit,unsigned...input>
struct LastResult<Cont<car_bit>,Cont<input...>>
{
    using result=std::conditional_t<car_bit,Cont<car_bit,input...>,Cont<input...>>;
};


int main()
{
    using type1=reverse<Cont<>,Cont<9,9,9>>::type;
    using type2=reverse<Cont<>,Cont<1>>::type;
    using t=Add<Cont<>,Cont<0>,type1,type2>::last_type;
    print<t>::fun();
}

example6:将m进制输入转换为n进制,如四进制的输入数组Cont<2,3>,转换为三进制的输出数组Cont<1,0,2>,程序中借用了example3和4

整个程序流程为,先翻转数组,之后转换为10进制数,再利用模n取余法逐步取余,得到最终结果输出。

constexpr unsigned m=4;
constexpr unsigned n=3;
/*****************************************************************************************
 * convert the input m_ary array to the decimal number
 *****************************************************************************************/
template <typename... T>
struct DecimalRes;

template <unsigned T1,unsigned... T>
struct DecimalRes<Cont<T1,T...>>
{
    constexpr static unsigned value=T1+m*DecimalRes<Cont<T...>>::value;
};

template <>
struct DecimalRes<Cont<>>
{
    constexpr static unsigned value=0;
};

/*****************************************************************************************
 * convert the decimal number to the n_ary
 *****************************************************************************************/
template <typename T1,typename T2>
struct N_ary;

template <unsigned remain,unsigned... result>  //save the each compute result with parameter result 
struct N_ary<Cont<remain>,Cont<result...>>     //parameter remain is the quotient of last step
{
    constexpr static unsigned bit_value=remain%n;
    constexpr static unsigned remain_value=remain/n;
    using output=typename N_ary<Cont<remain_value>,Cont<bit_value,result...>>::output;
};

template <unsigned... result>
struct N_ary<Cont<0>,Cont<result...>>  //stop the iteration of function when the parameter remain is 0 
{
    using output=Cont<result...>;
};

int main()
{
    using input=reverse<Cont<>,Cont<2,3>>::type;   //input array:Cont<2,3>   23(4)  
    constexpr unsigned Dresult=DecimalRes<input>::value;  //23(4) -> 11(10)
    using LastRes=N_ary<Cont<Dresult>,Cont<>>::output;    //11(10) -->102(3)
    print<LastRes>::fun(); 
}

总结

了解元编程的基本思想,熟悉常见三种程序的编写方式。

1. 编译期数组的编写

template<typename... T>
struct Cont;

2. 从编译器数组中读取元素的方法

利用偏特化或完全特化,在类模板的特化版本中

template <typename T>
struct print;
template <unsigned T1,unsigned... T>
struct print<Cont<T1,T...>>  
{
    constexpr static int x=T1;
};

3. 普通版本的模板调用看template<>里的内容,特化版本的要看模板名后的<>

如struct print<Cont<T1,T…>>

4. 注意歧义问题

形如在模板中定义T::xxx或T.xxx即在模板定义的内部,访问T的成员,则可能会引起歧义,必须加上template或typename进行说明。

例如:

using type=typename Imp<T1,T2>::type; 必须加上typename以说明type是一个类型。

引发歧义的原因,模板内使用了依赖性名称,其依赖于模板形参,如上,type间接依赖于T1,T2。必须声明type是一个类型,否则编译器认为type是一个静态数据成员,报错。

例如:

struct str
{
    template <typename T>
    static void internal()
    {
        
    }
}

template <typename T>
void fun()
{
    T::internal<int>();   //错误 应该为 T::tempalte internal<int>();消除歧义
    T obj;
    obj.internal<int>();  //错误 改为obj.tempalte internal<int>();
}

引发歧义的原因:inernal依赖于模板形参T,必须声明internal是一个函数模板,否则编译器会认为internal是一个成员,解析为internal<int导致错误

例如:

struct str
{
    using internal=int;
}
template <typename T>
void fun()
{
    T::internal*p;  //错误 改为typename T::internal*p;
}

引发歧义的原因:inernal依赖于模板形参T,必须声明internal是一个类型,否则编译器会认为internal是一个静态数据成员,解析为internal*p导致错误

总结:

  1. 形如typename T::internal*pusing type=typename Imp<T1,T2>::type,使用模板形参的内部类型,必须加typename
  2. 如T::tempalte internal()或T.obj; obj.tempalte internal(),调用模板形参的内部模板成员函数,必须加上template说明是一个模板。

5. 元编程部分编程思想

类内部数据成员选择constexpr static,函数内部数据成员选择constexpr,数组使用template<typename… T>
struct Cont
来表示,在类模板内部使用using type=Cont

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值