|
Boost : |
From: Vesa Karvonen (vesa.karvonen_at_[hidden])
Date: 2001-05-20 23:49:46
----- Original Message -----
From: "Aleksey Gurtovoy" <alexy_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Sunday, May 20, 2001 16:53
Subject: RE: [boost] Interest in CPP meta programming library?
[...]
> The macros themselves are in the
> 'mpl/enumeration_macros.hpp' and 'mpl/basic_macros.hpp' headers. Two-part
> naming of the enumerated macro ("BOOST_MPL_, LIST_FACTORY_SPEC") is a
> (historical) implementation artifact, but the rest is more or less
> equivalent to your BOOST_LST0 and BOOST_LST1 approach, except that the
names
> of these are much longer - BOOST_MPL_ENUMERATE_MACRO_CALLS and
> BOOST_MPL_ENUMERATE_USER_MACROS respectively :). Well, actually there are
> some differences in techniques, but IMO they are rather minor. I would
like
> to see both approaches merged into something simple and elegant that would
> become an official boost library :).
BOOST_MPL_ENUMERATE_USER_MACROS is similar to REPT except that it uses the
two-part naming and starts from 1.
BOOST_MPL_ENUMERATE_MACRO_CALLS makes it possible to pass two extra
parameters to the macro. It also treats the first element as a special case.
Summary of differences:
- special case for first element (to enable generating comma separated
lists)
- two (or some other number of) extra parameters for the macro invocation
Special case for first element
==============================
Treating the first element as a special case is one way to solve the problem
of creating lists. It has the advantage that the enumeration macro
implementation is slightly simpler. On the other hand, the user then needs
to implement both the general and the special case macros.
The LIST() primitive allows to use both a S[EPARATOR] and an E[LEMENT] and
then expands to the following sequence:
E(0) S(1) E(1) S(2) E(2) ... S(n) E(n)
This solves two problems:
- It solves the comma separated list generation problem
- It allows starting from either 0 or 1
For the above reasons, I find the approach taken by LIST, that is to have
both a SEPARATOR and an ELEMENT, to be the most elegant approach.
This is still not ideal, because if you want to start from 1 and still
generate a comma separated list, you need to write the last element
separately.
I have found the ability to start from either 0 or 1 quite useful. It makes
it possible to use "parameter shifting". The CPP Introduction has an example
that uses this feature in the Make_List<> meta function implementation (#1).
An alternative way to let the user start from 0 or 1 is to implement INC
and/or DEC token look-up functions:
#define INC(x) INC##x
#define INC0 1
#define INC1 2
// ...
#define DEC(x) DEC##x
#define DEC1 0
#define DEC2 1
// ...
However, INC and DEC don't solve the problem of having to use a special case
for the first or last element. Implementing INC and DEC and using them also
has some complications due to the low level nature of CPP. I will probably
add the INC and DEC into the demo library, so people can try them out to see
if they are worth keeping.
Two extra parameters
====================
As a design heuristic, allowing extra parameters may seem like it would make
the list more generic, but in my experience most "generalizations" that make
the implementation more complicated have turned out to be specializations.
A possible merging would be to allow giving parameters to the SEPARATOR and
ELEMENT macro:
#define LIST(n,S,E,P0,P1) CAT(LIST,n)(S,E,P0,P1)
#define LIST0(S,E,P0,P1)
#define LIST1(S,E,P0,P1) E(0,P0,P1)
#define LIST2(S,E,P0,P1) LIST1(S,E,P0,P1) S(1,P0,P1) E(1,P0,P1)
#define LIST3(S,E,P0,P1) LIST2(S,E,P0,P1) S(2,P0,P1) E(2,P0,P1)
// ...
This has the advantage that certain common situations could be implemented
in the CPP library code. For example:
#define ARG_LIST_WITH_A_DEFAULT(n,NAME,THE_DEFAULT)\
LIST(n, COMMA_FUN, ARG_WITH_A_DEFAULT, NAME, THE_DEFAULT)
#define ARG_WITH_A_DEFAULT(i, NAME, THE_DEFAULT) NAME##i = THE_DEFAULT
#define ARG_LIST(n,NAME)\
LIST(n, COMMA_FUN, ARG, NAME, unused)
#define ARG(i, NAME, unused) NAME##i
There is at least one complication however. Namely, if the NAME or
THE_DEFAULT have commas, the above technique break down. This is one of the
reasons why the original LIST specificly only accepts macros as arguments.
Anyway, you could then use them like this:
template
< ARG_LIST_WITH_A_DEFAULT(
MAKE_LIST_MAX_LENGTH,
class T,
NS(Private::)Make_List_End) >
struct Make_List
{
// Unfortunately, you would then need distinct versions in order
// to use parameter shifting.
Make_List
< ARG_LIST_FROM_1_TO(MAKE_LIST_MAX_LENGTH, T) >
// ...
};
I think that this has certain appeal, because you can then keep most of
#define's out of sight and the code is perhaps a bit easier to read, because
it doesn't have preprocessor statements, which don't fit into the regular
C++ syntax.
On the other hand, the extra parameters are often redundant. They seem to be
useful only for generating argument lists.
In summary:
- The two extra parameters make it possible to make certain common list
generation cases arguably easier to read with the expense of
- complicating the LIST implementation (how much this effects portability
and maximum length is an open question)
- complicating the implementation of macros used in conjuction with LIST
- the two extra parameters may not contain commas
- The original LIST already makes it possible to generate the similar
sequences easily
An alternative merging would be to retain the LIST() as is and implement
separate primitives for generating argument lists.
FOOTNOTES
#1 The Make_List<> meta function allows making a Cons<> list conveniently:
Make_List<char,short,int,long>::RET
Is_Same as
Cons<char,Cons<short,Cons<int,Cons<long> > > >
The Make_List<> also manages to do this using only O(n) preprocessed tokens
due to the parameter shifting and default template arguments, whereas the
TYPELIST macro system presented in Modern C++ Design uses O(n*n) tokens.
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk