Boost logo

Boost :

From: Paul Mensonides (pmenso57_at_[hidden])
Date: 2006-01-22 09:48:35


> -----Original Message-----
> From: boost-bounces_at_[hidden]
> [mailto:boost-bounces_at_[hidden]] On Behalf Of Tobias Schwinger

> Here are two questions regarding the BOOST_PP_IS_*ARY macros:

> Is it safe to use this technique with sequences, e.g.
>
> #define OPT_SEQ_CAT(seq) \
> BOOST_PP_IIF(BOOST_PP_IS_UNARY(seq),BOOST_PP_SEQ_CAT, \
> BOOST_PP_TUPLE_EAT(1))(seq)
>
> OPT_SEQ_CAT(-) // expands to nothing
> OPT_SEQ_CAT((b)(o)(o)(s)(t)) // expands to 'boost'
>
> ?!

Yes--with regard to what I say below. Those macros detect "parenthetic
expressions"--which are non-pathological (see below) sequences of preprocessing
tokens that begin with something that's parenthesized. E.g. these work also:
IS_UNARY((+) -) IS_UNARY((+)(not, unary))

What compilers/preprocessors do you usually use?

> And why do these components live in 'detail' (as opposed to
> 'tuple')? Because of
> Borland?

Because of Borland and others. The problem with these macros is that they don't
work on several supported compilers. On others, they work, but only sometimes
(IOW, they are unstable). In the latter case, it isn't just a simple case of
"if you pass it this, it doesn't work". Rather, it is the result of
fundamentally broken preprocessors that do things in weird orders (probably as
some sort of optimization scheme). The library can get away with using them
internally because the library is doing its best to force certain things to
happen "nearby" where they should happen--and its doing it all over the entire
library (look at the the VC configuration, for example). The result for client
use is highly unpredictable because client code doesn't contain the scaffolding
(the pp-lib bends over backward to minimize the this in user code). On
reasonably good preprocessors, this isn't a problem at all, and using them is
perfectly safe and predictable.

-----

Regarding your DO_UNARY-esque macros... They would be more general if they were
something like:

#define DO_UNARY(macro, x)
    BOOST_PP_IIF(BOOST_PP_IS_UNARY(x),macro,BOOST_PP_TUPLE_EAT(1)) \
    /**/

DO_UNARY(macro, x)(x)

Even though it is more verbose, it keeps the actual invocation of 'macro' out of
DO_UNARY (so-to-speak). The way you have it will fail if 'macro' tries to use
DO_UNARY. This isn't a big deal at a small scale, but it's what I call a
"vertical dependency". Vertical dependencies drastically lower the
encapsulation of macros, which creates difficult to diagnose problems. The way
that I have it won't fail if 'macro' tries to use DO_UNARY.

-----

BTW, with variadics, you can do all kinds of fun things related to
optional/default arguments or overloading based on number of arguments. Some
simple examples...

#define A_1(a) -a
#define A_2(a, b) a - b
#define A_3(a, b, c) a - b - c

#define A(...) \
    CHAOS_PP_QUICK_OVERLOAD(A_, __VA_ARGS__)(__VA_ARGS__) \
    /**/

A(1) // -1
A(1, 2) // 1 - 2
A(1, 2, 3) // 1 - 2 - 3

The QUICK_OVERLOAD macro is a constant-time operation, but is limited to
something like 25 arguments. There is also an OVERLOAD macro that doesn't have
that limitation, but isn't constant-time (To be technically accurate, it counts
n arguments in floor(n / 10) + n mod 10 + 2 steps.)

-----

// B(a, b = 123) -> a + b

#define B(...) \
    CHAOS_PP_NON_OPTIONAL(__VA_ARGS__) \
    + CHAOS_PP_DEFAULT(123, __VA_ARGS__) \
    /**/

B(1, 2) // 1 + 2
B(67) // 67 + 123

Note that there is no difference (because of placemarkers) between 0 and 1
arguments. Furthermore, emptiness cannot be detected without restricting input
(not counting what I call pathological input--which is something like passing
just LPAREN()). Because of that, optional arguments (with or without default
values) must always be "attached" to a non-optional argument--hence the name
NON_OPTIONAL. Beyond that, you can have any number of optional or default
arguments, but there must always be one that is required--which is fine for the
majority of circumstances.

// M(x, y = 2, z = 3) -> x + y + z

#define M(...) \
    CHAOS_PP_NON_OPTIONAL(__VA_ARGS__) \
    + CHAOS_PP_DEFAULT_AT(0, 2, __VA_ARGS__), \
    + CHAOS_PP_DEFAULT_AT(1, 3, __VA_ARGS__) \
    /**/

M(a) // a + 2 + 3
M(a, b) // a + b + 3
M(a, b, c) // a + b + c

The library itself uses optional arguments in a variety of places:

CHAOS_PP_AUTO_REPEAT(3, A, data)
    // A(s, 0, data) A(s, 1, data) A(s, 2, data)

CHAOS_PP_AUTO_REPEAT(3, B)
    // B(s, 0) B(s, 1) B(s, 2)

CHAOS_PP_AUTO_REPEAT(3, C, d1, d2)
    // C(s, 0, d1, d2) C(s, 1, d1, d2) C(s, 2, d1, d2)

(etc.)

As referred to above, the 'count' and 'macro' arguments are required, but the
algorithm makes the auxiliary data argument optional (as well as variadic) which
is propogates on to the called macro.

Regards,
Paul Mensonides


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