|
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