Boost logo

Boost :

From: Paul Mensonides (pmenso57_at_[hidden])
Date: 2002-04-17 14:38:53


To David Abrahams (specifically)...

> It's not bad, but I don't yet see how to get it to deal with another
> level of iteration. Repeat 'N' of my macro typically contains a
> construct like:
>
> <class T1, class T2, ... class T(N-1)>
>
> You can't just create make1.h, make2.h,... can you?

I did a little experimenting with this, and I came up with some interesting
conclusions....

One of the interesting things about the preprocessor meta-programming facilities
that we have but hardly ever use is that the argument to an include directive
can be a macro. That is a powerful facility. As it turns out, you can
relatively easily do the above. As I said before, the usage of such a mechanism
can get ugly if you take it too far, yet it is possible.

The solution hinges on several facts...
1. a macro can be the argument to an include directive
2. a macro can be a parameter to a file, if the 'file' is analogous to a macro
then a macro can be analogous to a macro parameter.

In order to test this, I created several files like the following (the postfix
number represents the level of iteration):
--------------------------------------------------
// [ file_exp.1.h ]

// file arguments:
// *FILE_DEPTH_1: file to include repeatedly
// LBOUND_DEPTH_1: lower bound of this inclusion
// *UBOUND_DEPTH_1: upper bound of this inclusion
//
// * required

// make sure the parameters to the file are in place
#if !defined(FILE_DEPTH_1) || !defined(UBOUND_DEPTH_1)
    #error FILE_DEPTH_1 && UBOUND_DEPTH_5 must both be defined
#endif

// provide default for LBOUND
#ifndef LBOUND_DEPTH_1
    #define LBOUND_DEPTH_1 1
#endif

// helper macro
#define RANGE_DEPTH_1(N) LBOUND_DEPTH_1 <= N && UBOUND_DEPTH_1 >= N

// perform expansion...

#define DEPTH() 1

#if RANGE_DEPTH_1(0)
    #define FILE_PARAM_1 0
    #include FILE_DEPTH_1
    #undef FILE_PARAM_1
#endif
#if RANGE_DEPTH_1(1)
    #define FILE_PARAM_1 1
    #include FILE_DEPTH_1
    #undef FILE_PARAM_1
#endif
// etc. up to 100

#undef DEPTH
--------------------------------------------------
I made five of these files: file_exp.1.h, file_exp.2.h, file_exp.3.h,
file_exp.4.h, and file_exp.5.h. These files are all the same except the postix
number on everything. Also, DEPTH() is defined appropriately, and on 2 through
5 it is defined back to what it was before rather than just undefined.

I then tested it with this sample file. The purpose of this file is to define
linearized typelists ala 'typelist<3>::args<int, int, int>' rather than
TYPELIST_3(int, int, int). Please note, once again I'm using my own
preprocessor facilities rather than the BPL. Here is the legend:

LIST_CLASS_T_DEF(N): class T1, class T2 = T1, class T3 = T2, ... class T(N) =
T(N - 1)
LIST_T_MINUS_FIRST(N): T2, T3, ... T(N)
--------------------------------------------------
// [ typelist_impl.h ]

#ifndef TYPELIST_IMPL_H
#define TYPELIST_IMPL_H

// typelist_t

template<class T, class U = null_t> struct typelist_t {
    typedef T head;
    typedef U tail;
};

// typelist

template<int> struct typelist;

template<> struct typelist<1> {
    template<class T1> struct args {
        typedef typelist_t<T1> type;
    };
};

#endif

template<> struct typelist<FILE_PARAM_1> {
    template<LIST_CLASS_T_DEF(FILE_PARAM_1)> struct args {
        typedef typelist_t<
            T1,
            typename typelist<FILE_PARAM_1 - 1>::args<
                LIST_T_MINUS_FIRST(FILE_PARAM_1)
>::type
> type;
    };
};
--------------------------------------------------
I tested it (with Comeau C++) like this:
--------------------------------------------------
// [ test.cpp ]

#include "other-facilities.h"

#define LBOUND_DEPTH_1 2
#define UBOUND_DEPTH_1 100
#define FILE_DEPTH_1 "typelist_impl.h"

#include "file_exp.1.h"

int main() {
    return 0;
}
--------------------------------------------------
Everything worked fine, and I got preprocessor output like this (for example):

template<> struct typelist<3> {
    template<class T1, class T2 = T1, class T3 = T2> struct args {
        typedef typelist_t<
            T1,
            typename typelist<3 - 1>::args<
                T2, T3
>::type
> type;
    };
};

Which is exactly what I wanted. Then I tested the iteration with further depth
by replacing LIST_CLASS_T_DEF and LIST_T_MINUS_FIRST with files:
--------------------------------------------------
// [ LIST_CLASS_T_DEF.h ]

#if LBOUND_DEPTH_2 == FILE_PARAM_2 || LBOUND_DEPTH_2 == UBOUND_DEPTH_2
    class CONCAT(T, FILE_PARAM_2)
#else
    , class CONCAT(T, FILE_PARAM_2) = CONCAT(T, DECR(FILE_PARAM_2))
#endif

--------------------------------------------------
// [ LIST_T_MINUS_FIRST.h ]

#if FILE_PARAM_2 != LBOUND_DEPTH_2
    #if FILE_PARAM_2 != INCR(LBOUND_DEPTH_2)
        ,
    #endif
    CONCAT(T, FILE_PARAM_2)
#endif
--------------------------------------------------
I then modified the 'typelist_impl.h' file as follows:
--------------------------------------------------
// [ typelist_impl.h ]

#ifndef TYPELIST_IMPL_H
#define TYPELIST_IMPL_H

// as before

#endif

#define UBOUND_DEPTH_2 FILE_PARAM_1

template<> struct typelist<FILE_PARAM_1> {
    template<
        #define FILE_DEPTH_2 "LIST_CLASS_T_DEF.h"
        #include "file_exp.2.h"
        #undef FILE_DEPTH_2
> struct args {
        typedef typelist_t<
            T1,
            typename typelist<FILE_PARAM_1 - 1>::args <
                #define FILE_DEPTH_2 "LIST_T_MINUS_FIRST.h"
                #include "file_exp.2.h"
                #undef FILE_DEPTH_2
>::type
> type;
    };
};

#undef UBOUND_DEPTH_2
--------------------------------------------------
This worked fine, but the preprocessor output was terribly ugly, because you get
a bunch of #line directives and empty lines between each 'class T(x)' for
example.

I thought this was pretty bad, but I tested the preprocessing speed anyway. It
turns out, that the version that uses only file-iteration (i.e. the 2nd example)
was roughly twice as slow as the the version that uses both file-iteration and
macro-recursion (i.e. the 1st example). The version that uses only
macro-recursion is *way* slower than either.

Any thoughts?

Paul Mensonides


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk