boost::preprocessor库之横向重复与纵向重复

来源:互联网 发布:雨血全剧情知乎 编辑:程序博客网 时间:2024/06/05 22:37

下面我们我们来看看pp库的另一大功能:代码重复产生。代码重复在模板类的特例化里非常有用,那能方便地帮我们产生数量众多的模板特例化。下面看一个简单的特例化模板产生的代码:

#include <boost/preprocessor/repetition/enum_params.hpp>

#define M 3

template <BOOST_PP_ENUM_PARAMS(M, class T)>
struct tiny_size
: mpl::int_<M>
{};

同样用g++ -P -E进行预编译,查看一下结果:

template < class T0 , class T1 , class T2>
struct tiny_size
: mpl::int_<3>
{};

可以看出,ENUM_PARAMS是专门用来重复生成参数列表代码的,它能够自动地在参数名称后面为我们加上索引号。这样,我们写出一个特例化模板的样例代码,就可以通过改变M值来方便地随意产生各种特例话模板。

那么我们如果要同时产生多个特例化呢?来看看这个稍微复杂点的例子:

#include <boost/preprocessor/repetition.hpp>
#include <boost/preprocessor/arithmetic/sub.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp>

#define M 3

#define TINY_print(z, n, data) data

#define TINY_size(z, n, unused) /
template <BOOST_PP_ENUM_PARAMS(n, class T)> /
struct tiny_size < /
BOOST_PP_ENUM_PARAMS(n,T) /
BOOST_PP_COMMA_IF(n) /
BOOST_PP_ENUM( /
BOOST_PP_SUB(M,n), TINY_print, none) /
> : mpl::int_<n> {};

BOOST_PP_REPEAT(M, TINY_size, ~)

#undef TINY_size
#undef TINY_print

首先,我们看到,我们包含了头文件 repetition.hpp,这个文件里包含了各种横向重复的宏,比如BOOST_PP_ENUMBOOST_PP_ENUM_PARAMSBOOST_PP_REPEAT,他们区别在于具体重复的模式,比如ENUM用来产生逗号分隔的重复,而内容可以由用户自己定义,比如我们这里的TINY_printENUM_PARAMS则允许生成参数模式的重复;而REPEAT则只重复,而不产生逗号分隔符。

我们还应该注意到,TINY_printTINY_size这两个宏的参数形式都是(z,n,d),其中z用做一些优化,我们可以暂时不用考虑,n则是重复时由pp库传入的当前重复的索引,d则为用户传入的token,如果我们不需要用户传入对象,则可以用符号’~'来占位。

最后,细心的读者也许还要问,为什么print和size这两个宏是小写字母?这是由于我们定义的这些宏将只在这块代码里有效,我们在重复结束后将其undef了,这样我们就不会影响宏的全局命名空间(宏里面可没有namespace这些高档玩意儿)。

COMMA_IF则根据参数是否不为0,来决定是否产生一个逗号。下面我们看看这段代码的输出:

template <> struct tiny_size < none , none , none > : mpl::int_<0> {}; template < class T0> struct tiny_size < T0 , none , none > : mpl::int_<1> {}; template < class T0 , class T1> struct tiny_size < T0 , T1 , none > : mpl::int_<2> {};

好了,请留下2分钟,仔细根据输出揣摩一下pp库的使用。这其中用到了两层的重复,最外一层使用REPEAT,每次重复生成一个完整的特例化模板类;第二层有两个重复,一个用ENUM_PARAMS来生成参数列表,而另一个使用ENUM来生成占位用的none字符串。

结果是正确了,可是,我们还是对结果非常不爽,因为,输出居然是一整行!首先,这非常不利于代码的阅读;其次,我们的debugger在报错时一般是按照代码的行号来进行报错的,而这样一整行,使得所有的错误都指向同一行!

造成这种不爽的原因在于,我们的宏总是在一行中完成,这使得横向的重复对于函数参数的重复来说非常适用,但对于大范围的重复,比如一个类的定义,则 不太适用了。当然,boost的guru们绝不善罢甘休,他们造出了纵向重复这个概念来解决这个问题。而纵向重复根据不同的实现细节又分成了LOCAL重 复,文件重复和自我文件重复三大类。我们首先看看用LOCAL重复来改善一下上面那段代码。

#include <boost/preprocessor/repetition.hpp>
#include <boost/preprocessor/arithmetic/sub.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp>
#include <boost/preprocessor/iteration/local.hpp>

#define M 3

#define TINY_print(z, n, data) data

#define TINY_size(z, n, unused) /
template <BOOST_PP_ENUM_PARAMS(n, class T)> /
struct tiny_size < /
BOOST_PP_ENUM_PARAMS(n,T) /
BOOST_PP_COMMA_IF(n) /
BOOST_PP_ENUM( /
BOOST_PP_SUB(M,n), TINY_print, none) /
> : mpl::int_<n> {};

//BOOST_PP_REPEAT(M, TINY_size, ~)
#define BOOST_PP_LOCAL_MACRO(n) TINY_size(~,n,~)
#define BOOST_PP_LOCAL_LIMITS (0,M-1)
#include BOOST_PP_LOCAL_ITERATE()


#undef TINY_size
#undef TINY_print

LOCAL_ITERATE()这个函数用来生成纵向重复代码,但它的输入要求有两个特殊的宏的定义:LOCAL_MACRO(n)LOCAL_LIMITS,前着在每次重复的时候调用,传入重复的索引,后者用来定义重复的范围。注意,我们在重复范围定义时,用的是0到M-1,所以,对于LIMITS(a,b),它实际重复的范围是[a,b],这是一个全闭的空间。

好了,简单地进行了这样一个改进,让我们来看看输出的结果:

template <> struct tiny_size < none , none , none > : mpl::int_<0> {};
template < class T0> struct tiny_size < T0 , none , none > : mpl::int_<1> {};
template < class T0 , class T1> struct tiny_size < T0 , T1 , none > : mpl::int_<2> {};

是不是好多了?当然,追求完美的我们还是不满意,毕竟虽然每个模板类被分配在了单独的一行,但每个模板类却还是在同一行。要继续改进,就需要用到文件重复以及自我重复。