Boost logo

Boost :

From: Paul Mensonides (pmenso57_at_[hidden])
Date: 2002-12-13 17:58:51

----- Original Message -----
From: "Paul Mensonides" <pmenso57_at_[hidden]>
> > Unfortunately I like all of the above except the last one. I'd even
> > like the last one, perhaps best of all, if it were:
> >
> > BOOST_WORKAROUND(__SUNPRO_CC, (?) <= 0x530)
> >
> > So I think I'll have to ask other people to weigh in here. Do you
> > have any preferences, anyone?

[Here's an implementation of this from scratch, because it is easier to
explain that way. If you don't care how it works, skip to the end which is
the final, clean solution (a lot cleaner that this!)]

We need several helper macros:

----- CAT -----

#define CAT(a, b) CAT_P(a, b)
#define CAT_P(a, b) a ## b

The macro delays to allow the arguments 'a' and 'b' to expand if necessary.
This avoids having to have delays in the macros elsewhere.

----- ELEM_x -----

#define ELEM_0(a, b) a
#define ELEM_1(a, b) b

These two macros simply return the first or second argument depending on the
suffix. These two macros form the shared implementation of the next two.

----- IIF -----

#define IIF(c, t, f) CAT(ELEM_, cond)(f, t)

If 'c' is 1 then this macro returns 't'. If 'c' is 0, it returns 'f'.
[Note: I use the name "IIF" here to distinguish it from "IF" which performs
a boolean conversion on the condition operand.] In effect, "CAT_P(ELEM_,
cond)" returns either "ELEM_0" or "ELEM_1" which immediately expands against
"(f, t)".

----- SPLIT -----

#define SPLIT(n, im) CAT(ELEM_, n)(im)

This one is trickier. The argument 'im' is a single argument that
immediately expands to two arguments (in and of itself). This macro returns
either the first or second of those two arguments. E.g.

    #define NAME Abrahams, David

    SPLIT(0, ABC) // Abrahams
    SPLIT(1, ABC) // David

----- IS_UNARY -----

#define IS_UNARY(expr) /* ... */ \
    SPLIT( \
        0, \
        CAT( \
            IS_UNARY_, \
            IS_UNARY_CHECK expr \
        ) \
    ) \
#define IS_UNARY_CHECK(_) 1

#define IS_UNARY_1 1, NIL

This one is the most complicated. 'expr' must begin with either a unary
parenthesisized expression or not--for example: "(!) 0x0" vs. "<= 0x0". In
particular, it *MUST NOT* be a binary parenthesized expression (such as "(1,
2)"). If 'expr' begins with "(...)" (where the ellipsis is a placeholder
for a single macro parameter) this macro expands to 1, otherwise it expands
to 0. How it does this is simple. It attempts to expand a unary macro by
placing it in front of 'expr'. If that macro expands, it will yield 1. The
result of this "attempt" to expand a unary macro is then concatenated to
"IS_UNARY_". This concatenation can result in two things: IS_UNARY_1 or
IS_UNARY_IS_UNARY_CHECK--depending on whether or not the test macro
expanded. Both of these are macros that expand to a binary
comma-expression. The reason they don't just expand to 1 and 0 is because
we need to get rid of everything that trails the original 'expr'. For
example, with "(!) 0x0" will end up being "1, 0x0 NIL" at this point, and
"<= 0x0" will end up being "0, <= 0x0 NIL". Altogether the "CAT(...)" in
the above is a single argument that immediately expands into two
arguments--which is the type of parameter that SPLIT accepts. Therefore, we
use SPLIT to get the first of these two results, and ignore the trailing
junk value.

So, ultimately:

    IS_UNARY( <= 0x00 ) // 0
    IS_UNARY( (!) <= 0x00 ) // 1

The last helper we need is a way to get the exclamation point (!) out of the
expression "test" (e.g. "(x) expr" in this example) and a way to extract the
the expression that follows:

#define OP(a) a,

SPLIT(0, OP (x) expr) // yields: x
SPLIT(1, OP (x) expr) // yields: expr

So if we have the necessary extractors for something like this:

    SPLIT(0, (!) <= 0x123) // yields: !
    SPLIT(1, (!) <= 0x123) // yields: <= 0x123

Next we need two control path macros that represent the "normal" usage and
the "extended" usage:

#define NORMAL(symbol, test) && (symbol test)
#define EXTENDED(symbol, test) \
    && 1 / ( \
                BOOST_PP_SPLIT(0, OP test) (symbol BOOST_PP_SPLIT(1, OP
test)) \
                ? 0 : 1 \
            ) \

And finally,

#define BOOST_WORKAROUND(symbol, test) \
    ( \
        ((symbol != 0) IIF(IS_UNARY(test), EXTENDED, NORMAL)((symbol), test)
    ) \

Here are the results:

    // yields true if SYMBOL is less than 300

    // yields true if SYMBOL is less than or equal to 300 or if
    // BOOST_DETECT_OUTDATED_WORKAROUNDS is not defined (or zero)
    // otherwise it issues a warning/error if SYMBOL is _not_ less or equal
to 300


----------- final implementation -----------

The PP lib already contains most of this stuff and accounts for buggy
preprocessors as well:

#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/control/iif.hpp>
#include <boost/preprocessor/detail/is_unary.hpp>
#include <boost/preprocessor/detail/split.hpp>


#define BOOST_WORKAROUND_NORMAL(symbol, test) && (symbol test)

#define BOOST_WORKAROUND_EXTENDED(symbol, test) \
    && 1 / ( \
           (symbol BOOST_PP_SPLIT(1, BOOST_WORKAROUND_OP test)) ? 0 : 1 \
       ) \

#define BOOST_WORKAROUND(symbol, test) \
    ( \
        ((symbol != 0) \
        BOOST_PP_IIF( \
            BOOST_PP_IS_UNARY(test), \
        )((symbol), test) \
    ) \

[Note: The BOOST_PP_IS_UNARY macro will detect the difference between "(x)"
and "x"--regardless of what 'x' is (as long as it doesn't contain a
non-parenthesis-nested comma). This macro doesn't work to its full
potential on all the preprocessors that use the Borland configuration. It
will work fine in this scenario though.]

[Note: The reason that I'm using "(!)" is that it warnings/errors when the
expression: "symbol op value" is _not_ true. This, of course, is
equivalent to "(~)" but not "(?)". The actual operator has to be used
somehow to enforce the proper syntax, "(?)" could be used this way because
of the ternary conditional if you want.]

Paul Mensonides

Boost list run by bdawes at, gregod at, cpdaniel at, john at