template <classTag> structinsert_impl { template <classR, classU, classB, classI, classE> structapply_impl { using inner = typename push_back<R, typename deref<I>::type>::type; using type = typename apply_impl<inner, U, B, typename next<I>::type, E>::type; };
template <classR, classU, classI, classE> structapply_impl<R, U, I, I, E> { using inner = typename push_back<R, U>::type; using inner2 = typename push_back<inner, typename deref<I>::type>::type; using type = typename apply_impl<inner2, I, U, typename next<I>::type, E>::type; };
template <classR, classU, classB, classE> structapply_impl<R, U, B, E, E> { using type = R; };
template <classR, classU, classE> structapply_impl<R, U, E, E, E> { using type = typename push_back<R, U>::type; };
template <classT, classB, classU> structapply { using init = typename clear<T>::type; using type = typename apply_impl<init, U, B, typename begin<T>::type, typename end<T>::type>::type; }; };
template <classR, classU, classB, classI, classE> structapply_impl { using inner = typename push_back<R, typename deref<I>::type>::type; using type = typename apply_impl<inner, U, B, typename next<I>::type, E>::type; };
该元函数非常简单,通过push_back将原序列的元素插入到新序列中,其中I是迭代器。
template <classR, classU, classI, classE> structapply_impl<R, U, I, I, E> { using inner = typename push_back<R, U>::type; using inner2 = typename push_back<inner, typename deref<I>::type>::type; using type = typename apply_impl<inner2, I, U, typename next<I>::type, E>::type; };
template <classR, classU, classB, classE> structapply_impl<R, U, B, E, E> { using type = R; };
template <classR, classU, classE> structapply_impl<R, U, E, E, E> { using type = typename push_back<R, U>::type; };
最后两个特化版本的apply_impl限定了元函数的结束条件。一方面apply_impl<R, U, B, E, E>,当原序列遍历到结束迭代器时,如果插入目标位置不是结束迭代器,则插入操作直接结束,返回新序列。另一方面apply_impl<R, U, E, E, E>,当原序列遍历到结束迭代器时,如果插入目标位置正好是结束迭代器,那么就将目标元素U插入到新序列的末尾。
以下是一个调用insert元函数的示例:
using insert_list = list<int, bool, char>; using result_list = insert<insert_list, short, begin<insert_list>::type>::type;
template <template <class...> classT, classN, classU, classB, class... Args> structapply<iterator<T<U, Args...>, N, B>> { using type = U; };
template <template <class...> classT, classN, classB> structapply<iterator<T<>, N, B>> { using type = none; }; };
deref_impl和next_impl一样也是针对迭代器的元函数,它对迭代器进行解引用操作,随后可以获得元素本身。观察deref_impl的内嵌apply元函数,它也有两个特化版本。当其实参的迭代器为iterator<T<U, Args...>, N, B>时,说明它不是最后一个迭代器,于是返回当前元素U。当实参的迭代器为iterator<T<>, N, B>时,说明这是最后一个迭代器,它不包含任何元素,所以返回none。这里的none是YAMPL专门为类似这种情况定义的类型,用来表示没有意义的结果,具体定义如下:
structnone_tag {}; structnone { using tag = none_tag; };
template <classT, classN, classB = void, class B2 = void, class B3 = void> struct iterator { using type = T; using index = N; using backup = B; using backup2 = B2; using backup3 = B3; using tag = iterator_tag; };
template <classT> structnot_ { using value_type = bool; using type = integral_const<value_type, !T::value>; staticconstexpr value_type value = type::value; };
In instantiation of 'struct yampl::DbgPrintType<yampl::integral_const<int, 8> >': required from here warning: comparison of integer expressions of different signedness: 'long long unsigned int' and 'int' [-Wsign-compare] 15 | enum { n = sizeof(T) > -1 };
MSVC显示的警告为:
warning C4200: nonstandard extension used: zero-sized array in struct/union message : This member will be ignored by a defaulted constructor or copy/move assignment operator message : see reference to class template instantiation 'yampl::DbgPrintType<yampl::integral_const<T,8>>' being compiled with [ T=int ]
template <classT> structnegate { using value_type = typename T::value_type; using type = integral_const<value_type, -T::value>; staticconstexpr value_type value = type::value; };
综合上述运算符元函数,我们来做一道计算题-((5+(10-2)*3*5/2) << 2):
using step1 = minus<int_<10>, int_<2>>; // step1 = 10-2 using step2 = times<typename step1::type, int_<3>, int_<5>>; // step2 = step1*3*5 using step3 = divides<typename step2::type, int_<2>>; // step3 = step2/2 using step4 = plus<int_<5>, typename step3::type>; // step4 = 5+step3 using step5 = left_shift<typename step4::type, int_<2>>; // step5 = step4 << 2 using result_step = negate<typename step5::type>; // result_step = -step5 auto result_value = result_step::value;
#define BINARY_SINGLE_OP_BOOL(name, op) \ template <class N1, class N2> \ struct name { \ using value_type = bool; \ using type = integral_const<value_type, (N1::value op N2::value)>; \ static constexpr value_type value = type::value; \ }
从本篇开始,我们将开始进入轻量级C++模板元编程库YAMPL(Yet Another MPL)的编写环节。不过,在深入探索元编程的序列和算法之前,有一些基础工作必须完成,比如定义命名空间、定义整型常量包装类模板,编写包装类的算数和逻辑运算元函数、编写用于调试的类型打印元函数等等。在完成了这些基础组件后,序列和算法的编写工作将会变得非常高效和有趣。
template <classT, T N> structintegral_const { using value_type = T; using type = integral_const; staticconstexpr value_type value = N; using next = integral_const<T, N + 1>; using prior = integral_const<T, N - 1>; };
在上面的代码中,integral_const是整型常量包装类模板,其模板形参T是包装常量的类型,形参N是包装的具体数值,例如:integral_const<int, 5>是一个包装了数值为1的int类型常量的类型。integral_const定义了静态数据成员value来返回包装类型代表的具体数值,定义value_type来指示返回数值的准确类型。另外为了元函数调用形式上的统一,integral_const还定义内嵌类型type为自身。最后我们还可以发现,integral_const为了方便完成数值的自增和自减操作,分别定义了next和prior来表示integral_const<T, N + 1>和integral_const<T, N - 1>,于是我们可以完成这样的操作:
template <classT> structresult_wrap { using type = T; };
template <classS> structseq_is_all_reference;
template <classT, class... Args> structseq_is_all_reference<seq<T, Args...>> { using cond = typename std::is_reference<T>::type; using result = typename if_<cond, seq_is_all_reference<seq<Args...>>, result_wrap<false_type>>::type; using type = typename result::type; };
template <classT> structseq_is_all_reference<seq<T>> { using cond = typename std::is_reference<T>::type; using type = typename if_<cond, true_type, false_type>::type; };
template <int a, int b> structplus { staticconstexprint value = a + b; };
看到以上代码,有人可能会惊呼道:“这哪是什么函数,它明明就是一个类模板!”。是的没错!它确实是一个类模板,可同时也是一个元函数。让我们先接受这个定义,这样可以让我们很容易的找到它和普通函数版本的对应关系:首先plus作为类模板名也是元函数名,然后类模板形参int a, int b对应是元函数的形参,最后元函数的函数体就是类模板的定义。虽然在元函数中我们没有办法定义返回类型(实际上也没有必要定义,因为大多数时候元函数返回的就是类型本身),但是可以通过约定元函数的函数体中静态变量名称的方法定义数值的返回值,体现在plus中即为static constexpr int value = a + b;,通常情况下,元函数会约定value为数值类型的返回值名称。
另外还有一种特殊的情况,当元函数没有形参时,对应的则是类或者实例化的类模板。比如:
structkilometer { staticconstexprint value = 1000; };
MetaWare High C/C++ Compiler R2.6 (c) Copyright 1987-94, MetaWare Incorporated E "primes.cpp",L16/C63(#416): prim | Type `enum{}´ can´t be converted to txpe `D<2>´ ("primes.cpp",L2/C25). -- Detected during instantiation of Prime_print<30> at "primes.cpp",L21/C5: E "primes.cpp",L11/C25(#416): prim | Type `enum{}´ can´t be converted to txpe `D<3>´ ("primes.cpp",L2/C25). -- Detected during instantiation of Prime_print<30> at "primes.cpp",L21/C5: E "primes.cpp",L11/C25(#416): prim | Type `enum{}´ can´t be converted to txpe `D<5>´ ("primes.cpp",L2/C25). -- Detected during instantiation of Prime_print<30> at "primes.cpp",L21/C5: E "primes.cpp",L11/C25(#416): prim | Type `enum{}´ can´t be converted to txpe `D<7>´ ("primes.cpp",L2/C25). -- Detected during instantiation of Prime_print<30> at "primes.cpp",L21/C5: E "primes.cpp",L11/C25(#416): prim | Type `enum{}´ can´t be converted to txpe `D<11>´ ("primes.cpp",L2/C25). -- Detected during instantiation of Prime_print<30> at "primes.cpp",L21/C5: E "primes.cpp",L11/C25(#416): prim | Type `enum{}´ can´t be converted to txpe `D<13>´ ("primes.cpp",L2/C25). -- Detected during instantiation of Prime_print<30> at "primes.cpp",L21/C5: E "primes.cpp",L11/C25(#416): prim | Type `enum{}´ can´t be converted to txpe `D<17>´ ("primes.cpp",L2/C25). -- Detected during instantiation of Prime_print<30> at "primes.cpp",L21/C5: E "primes.cpp",L11/C25(#416): prim | Type `enum{}´ can´t be converted to txpe `D<19>´ ("primes.cpp",L2/C25). -- Detected during instantiation of Prime_print<30> at "primes.cpp",L21/C5: E "primes.cpp",L11/C25(#416): prim | Type `enum{}´ can´t be converted to txpe `D<23>´ ("primes.cpp",L2/C25). -- Detected during instantiation of Prime_print<30> at "primes.cpp",L21/C5: E "primes.cpp",L11/C25(#416): prim | Type `enum{}´ can´t be converted to txpe `D<29>´ ("primes.cpp",L2/C25).
<source>:12: error: initializing argument 1 of 'D<i>::D(void*) [with int i = 17]' ... <source>:12: error: initializing argument 1 of 'D<i>::D(void*) [with int i = 13]' ... <source>:12: error: initializing argument 1 of 'D<i>::D(void*) [with int i = 11]' ... <source>:12: error: initializing argument 1 of 'D<i>::D(void*) [with int i = 7]' ... <source>:12: error: initializing argument 1 of 'D<i>::D(void*) [with int i = 5]' ... <source>:12: error: initializing argument 1 of 'D<i>::D(void*) [with int i = 3]' ... <source>:12: error: initializing argument 1 of 'D<i>::D(void*) [with int i = 2]'
test.cpp: In instantiation of 'struct DbgPrintType<prime_values<17, 13, 11, 7, 5, 3, 2> >': test.cpp:62:3: required from here test.cpp:53:24: warning: comparison of integer expressions of different signedness: 'long long unsigned int' and 'int' [-Wsign-compare] 53 | enum { n = sizeof(T) > -1 }; | ~~~~~~~~~~^~~~
在后面的内容中,我们首先将接触到序列和元函数的概念以及它们的习惯用法。然后我们会使用序列和元函数完成基本的判断和循环操作。以上是模板元编程的基础部分,在此之后我们将实现一套轻量级的模板元编程库YAMPL(Yet Another MPL),YAMPL在接口上将非常接近Boost的MPL。请注意,实现YAMPL的目的并不是取代MPL,而是让我们牢牢掌握模板元编程的一种手段。