元编程
元编程的定义
定义:
元编程(Metaprogramming)是指某类计算机程序的编写,这类计算机程序编写或者操纵其他程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作。(百度上的定义)
简单来说就是能在编译器处理一些程序,或进行一些运算就算元编程。
形式:
- 模板
template <int x>
struct M
{
constexpr static int bval=x+1;
};
int main()
{
return M<5>::val; //编译器得到val=4
}
- constexpr函数
constexpr int fun(int x)
{
return x+1;
}
constexpr int val=fun(3); //编译器调用函数得到val=4
- 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
}
分支代码
- 基于 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;
}
}
- 基于特化
template <int x>
struct Imp
{
constexpr static int value=x*2;
};
template <>
struct Imp<100>
{
constexpr static int value=100-3;
};
- 基于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;
}
- 基于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的条件不满足,否则一定调用第二个类模板。
- 基于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导致错误
总结:
- 形如typename T::internal*p或using type=typename Imp<T1,T2>::type,使用模板形参的内部类型,必须加typename
- 形如T::tempalte internal()或T.obj; obj.tempalte internal(),调用模板形参的内部模板成员函数,必须加上template说明是一个模板。
5. 元编程部分编程思想
类内部数据成员选择constexpr static,函数内部数据成员选择constexpr,数组使用template<typename… T>
struct Cont来表示,在类模板内部使用using type=Cont。