模板元编程实战,第一章节.
#include "commom.hpp" #include <gtest/gtest.h>
元函数介绍
元函数会在编译期被调用与执行。在编译阶段,编译器只能构造常量作为其中间结果,无法构造并维护可以记录系统状态并随之改变的量,因此编译期可以使用的函数(即元函数)只能是无副作用的函数。
constexpr int func (int a) { return a + 1 ; }TEST (chapter1, _1_1_1){ constexpr int a = func (3 ); int b = 4 ; int c = func (b); ic (a, c); }
类型元函数
如果说上面的函数是操作\(y=f(x)\) ,那么他的输入是一个数值.但其实在c++中我们可以把类型看作是一种数值,对类型进行计算.
template <typename T>struct Func_ { using type = T; }; template <>struct Func_ <int >{ using type = uint64_t ; }; template <>struct Func_ <uint32_t >{ using type = uint64_t ; };
不过上面的元函数表述方法太过繁琐,我们可以用更加简化的方式来调用,由于using的时候默认会认为你在声明namespace,所以需要加上typename修饰来表明这是一个类型.
template <typename T>using Fun = typename Func_<T>::type;TEST (chapter1, _1_1_2){ Fun<int > a = 0x1 ; ic (std::is_same<decltype (a), uint64_t >::value); }
各种元函数表示方法
template <int a>constexpr int no_struct_fun = a + 1 ; template <int a>struct struct_fun { using type = int ; using ref_type = int &; using const_ref_type = const int &; constexpr static size_t size = sizeof (int ); };
模板类型参数与容器模板
模板元编程最重要的就是把类型也看作是一种数据,要知道我们编写的程序在编译时必然被编译器存储,那么代码的类型也是一种变量存储在编译器中的,因此我们合理地调用类型数据,可以发挥更大的作用.
模板作为元函数的输入
NOTE
我们可以传入一个模版类型,这个模板类型可以接收多个一个或多个模板类型的,此时对应的数学表达式类似于:
\[
\text{Func}(T_1,t_2)=T_1(t_2)
\]
template <template <typename > class T1 , typename T2>struct TypeCall_ { using type = typename T1<T2>::type; }; template <template <typename > class T1 , typename T2>using TypeCall = typename TypeCall_<T1, T2>::type;TEST (chapter1, _1_2_1){ TypeCall<std::remove_reference, int &> h = 3 ; ic (h); }
模板作为元函数的输出
NOTE 其实我个人觉得这只能算是多个元函数的compose,元函数中很
template <int AddorRemoveRef>struct OptFunc_ ;template <>struct OptFunc_ <0 >{ template <typename T> using type = std::add_lvalue_reference<T>; }; template <>struct OptFunc_ <1 >{ template <typename T> using type = std::remove_reference<T>; }; template <typename T, int AddorRemoveRef>using OptFunc = typename OptFunc_<AddorRemoveRef>::template type<T>;TEST (chapter1, _1_2_2){ OptFunc<int , 1 >::type h = 1 ; ic (h); }
容器模板
容器模板就是一种可以保存数值数据或者类型数据的一个容器.他就是一个类型,但是他可以保存以上两种数据.
template <int ... Vals>struct IntContainer { }; template <typename ... Types>struct TypeContainer { }; template <template <typename > typename ... Types>struct TemplateContainer { }; template <template <typename ...> typename ... Types>struct TemplateContainer2 { };
编译期实现分支、循环
典型的顺序执行元函数
template <typename T>struct RemoveCV_ { private : using inner_type = typename std::remove_reference<T>::type; public : using type = typename std::remove_reference<inner_type>::type; }; template <typename T>using RemoveCV = typename RemoveCV_<T>::type;TEST (chapter1, _1_3_1){ RemoveCV<const int &> h = 1 ; ic (h); }
分支执行的代码
NOTE
其实分支执行的方式有好多,我自己都能写出好几个,但是找到一个比较通用优雅的写法可能还挺难
通过conditional实现分支,
这种方法就是用在结构体模板继承时使用的(通过选择来继承类型,然后得到当前的值)
template <int T>struct IsOdd_ : std::conditional_t <(T % 2 ) == 1 , std::true_type, std::false_type>{ }; template <int T>constexpr bool IsOdd = IsOdd_<T>::value;
通过特化匹配来实现分支,比如我们设计了一个isfloat结构体,默认都是false,通过类型分发的可以自定义不同类型是否是float.
template <typename T>struct isFloat_ : std::false_type{ }; template <>struct isFloat_ <float > : std::true_type{ }; template <>struct isFloat_ <uint64_t > : std::true_type{ }; template <typename T>constexpr auto isFloat = isFloat_<T>::value;
std::enable_if来实现分支,这个是比较好用的,下面就是一个简单的应用.
首先利用enable_if来匹配当前参数的大致类型,然后对于array类型,写了一个traits去获得他的size,然后再for循环.这其实就是任意类型打印的雏形了.
template <typename T>struct is_array : std::false_type{ }; template <typename T, size_t N>struct is_array <std::array<T, N>> : std::true_type{ }; template <typename T>constexpr bool is_array_v = is_array<T>::value;template <typename T>struct array_traits { }; template <typename T, size_t N>struct array_traits <std::array<T, N>>{ constexpr static size_t size = N; }; template <typename T>std::enable_if_t <is_array_v<T>, void > print_any (const T &v) { std::cout << "arr :" ; for (size_t i = 0 ; i < array_traits<T>::size; i++) { std::cout << v[i] << " , " ; } std::cout << std::endl; } template <typename T>std::enable_if_t <std::is_arithmetic_v<T>, void > print_any (const T &v) { std::cout << "value :" << v << std::endl; } TEST (chapter1, _1_3_2){ ic (IsOdd<1 >, IsOdd<2 >); ic (isFloat<float >, isFloat<uint64_t >, isFloat<double >); std::array<float , 10> arr = {1 , 2 , 3 , 4 , 5 , 6 , 7 }; print_any (arr); print_any (true ); }
循环执行的代码
通常我们需要用递归的方式进行执行
template <size_t Input>constexpr size_t Onescount = (Input % 2 ) + Onescount<(Input / 2 )>;template <>constexpr size_t Onescount<0 > = 0 ;TEST (chapter1, _1_3_3){ constexpr size_t res = Onescount<45 >; ic (res); }
练习
练习1
构造一个输入为类型输出为值的元函数
template <typename T>struct get_type_size : std::integral_constant<size_t , sizeof (T)>{ }; TEST (chapter1, practice_1){ ic (get_type_size<std::tuple<int , float , double >>::value); ic (get_type_size<float >::value); }
练习2
元函数的输入参数甚至可以是类型与数值混合的。尝试构造个元函数,其输入参数为一个类型以及一个整数。如果该类型所对应对象的大小等于该整数,那么返回true,否则返回
false。
template <typename T, size_t N>struct get_type_size2 : std::bool_constant<sizeof (T) == N>{ }; TEST (chapter1, practice_2){ ic (get_type_size2<std::tuple<int , float , double >, 16 >::value); ic (get_type_size2<float , 16 >::value); ic (get_type_size2<uint64_t , 8 >::value); }
练习3
其他的元函数表现形式
如果我们所有的操作都是在操作类型,我们可以用继承的方式把类型进行传递,这样就不需要中间变量.
当然在类型操作不足的时候,我们可以利用一些操作补足他们,比如下面这个例子就是先利用一个constexpr函数求值,此时这个值的类型是integral_constant类型,我们再decltype得到他的类型,再获取他的值.(这是个简单的例子,可能看不出这样有什么方便的)
template <size_t A, size_t B>constexpr auto add () { return std::integral_constant <size_t , A + B>(); } template <size_t A, size_t B>struct add_ : decltype (add <A, B>()){ }; TEST (chapter1, question_3){ ic (add_<1 , 2 >::value); }
练习4
构造一个元函数,返回另一个元函数
template <typename T>struct reduce { using type = std::integral_constant<size_t , 0 >; }; template <size_t A, size_t B>struct reduce <std::index_sequence<A, B>>{ using type = std::integral_constant<size_t , A + B>; }; template <size_t A, size_t B, size_t ... Ns>struct reduce <std::index_sequence<A, B, Ns...>>{ using type = typename reduce<std::index_sequence<A + B, Ns...>>::type; }; TEST (chapter1, question_4){ ic (reduce<std::index_sequence<1 , 2 , 3 , 4 >>::type::value); }
练习5
使用
SFINAE构造一个元函数:输入一个类型T,当T存在子类型type时该元函数返回true,否则返回
false
template <typename T, typename = void >struct has_type : std::false_type{ }; template <typename T>struct has_type <T, std::void_t <typename T::type>> : std::true_type{ }; TEST (chapter1, question_5){ ic (has_type<reduce<std::index_sequence<1 , 2 , 3 , 4 >>>::value); ic (has_type<int >::value); }
练习6
使用在本章中学到的循环代码书写方式,编写一个元函数,输入一个类型数组,输出一个无符号整型数组,输出数组中的每个元素表示了输入数组中相应类型变量的大小。
template <typename ... TArgs>struct get_sizes { constexpr static std::array<size_t , sizeof ...(TArgs)> arr = {sizeof (TArgs)...}; }; TEST (chapter1, question_6){ ic (get_sizes<int , float , double , int8_t , uint32_t >::arr); }
练习7
使用分支短路逻辑实现一个元函数,给定一个整数序列,判断其中是否存在值为1
的元素。如果存在,就返回true,否则返回 false
template <size_t V>constexpr bool is_zero = (V == 0 );template <bool cur, typename TNext>constexpr static bool AndValue = false ;template <typename TNext>constexpr static bool AndValue<true , TNext> = TNext::value;template <typename T>struct has_one { constexpr static bool value = false ; }; template <size_t V>struct has_one <std::index_sequence<V>>{ constexpr static bool value = is_zero<V>; }; template <size_t V, size_t ... Ns>struct has_one <std::index_sequence<V, Ns...>>{ constexpr static bool cur_is_zero = is_zero<V>; constexpr static bool value = AndValue<cur_is_zero, has_one<std::index_sequence<Ns...>>>; }; TEST (chapter1, question_7){ ic (has_one<std::index_sequence<0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 >>::value); ic (has_one<std::index_sequence<0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 >>::value); ic (has_one<std::index_sequence<0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 >>::value); }