Boost logo

Boost :

Subject: Re: [boost] [preprocessor] Variadics suggestion
From: Paul Mensonides (pmenso57_at_[hidden])
Date: 2012-10-06 03:19:14


On Fri, 05 Oct 2012 21:16:50 -0700, paul Fultz wrote:

>> 3) For those of you familiar with Chaos, how many actually use the
>> lambda mechanism?  I've been internally debating whether to preserve it
>> for years.  It complicates things.  The other thing is whether
>> C90/C++98 should be supported (i.e. variadic/placemarker-less mode). 
>> Currently, Chaos does support these, and the lack of them in some cases
>> leads to interesting techniques (which is half the reason for Chaos in
>> first place).
>
> First, I think it should just support C99/C++11, for older preprocessors
> Boost PP can be used.

The biggest reason that I consider leaving C90/C++03 support in place is
because there are several techniques that would otherwise not get used--
and Chaos is my repository of techniques which are fleshed out enough to
be useful.

> I never use the lambda expression because they can be very slow, but I
> thought it would be nice to support some generic "invokable" expressions
> so perhaps supporting a form of bind for macros, like this:
>
>     CHAOS_PP_EXPR(CHAOS_PP_REPEAT(3, CHAOS_PP_BIND(CHAOS_PP_CAT, T,
>     _1)) // T0 T1 T2

I personally never use the lambda expressions either, though the speed is
not usually a big issue (since I'm not usually generating huge things).
However, I can't define _1 et al--at least permanently, and having a
separate macro tends to make things clearer (mnemonic names, etc.) rather
than more difficult.

One of the things that I've briefly been playing with--which is not
fleshed out, and I'm not can be fleshed out--is doing lambda in a
difficult way. The basic (seed of an) idea is to use a double (and maybe
triple) rail wall. Essentially, what Chaos' calls a "rail" is some macro
expression that holds itself, through any number of invocations, until
some context is reached. For example:

#define FLIP(...) __VA_ARGS__
#define FLOP(...) __VA_ARGS__

#define WAIT(macro) \
    CHAOS_PP_IIF(CHAOS_PP_IS_NULLARY(macro(())))( \
        WAIT_I, CHAOS_PP_EAT \
    )(macro) \
    /**/
#define WAIT_ID() WAIT
#define WAIT_I(macro) CHAOS_PP_DEFER(WAIT_ID)()(macro)

#define SCAN(...) __VA_ARGS__

With the above, you can delay the invocation of a macro arbitrarily with
something like:

MACRO WAIT(FLIP)(ARGS)

Essentially, this just expands to

MACRO WAIT_ID ()(FLIP)(ARGS)

over and over again until the FLIP context is detected which then results
in the deferred expression:

MACRO ( ARGS )

In reality, you'd have to use rail-specific implementations of IIF and
IS_NULLARY so that the rail can go through things like regular IIF and
SPLIT (inside IS_NULLARY, if I recall correctly).

So, if we have

#define ARG(n) ARG_A WAIT(FLIP)(n)
#define ARG_A(n) ARG_B WAIT(FLOP)() ) (,,,,, n) (
#define ARG_B() CHAOS_PP_EMPTY(

// here the (,,,,, n) is a placeholder some encoding that is way out
// past LIMIT_TUPLE or some value like that.

#define APPLY(...) \
    APPLY_A( \
        SCAN( ( FLIP(__VA_ARGS__) ) ) \
    ) \
    /**/
#define APPLY_A(...) __VA_ARGS__

APPLY( inline ARG(1)( const ARG(1)& ); )

This produces the somewhat unglodly-looking:

(inline ARG_B WAIT_ID ()(FLOP)() ) (,,,,, 1) (( const ARG_B WAIT_ID ()
(FLOP)() ) (,,,,, 1) (& );)

However, that can be simplified to pseudo-show what would happen on the
FLOP by replacing ARG_B WAIT_ID()(FLOP)() with "EMPTY("

(inline "EMPTY(" ) (,,,,, 1) (( const "EMPTY(" ) (,,,,, 1) (& );)

Still doesn't look fun, but this can be parsed:

( inline "EMPTY(" )
( ,,,,, 1 )
(
  ( const "EMPTY(" )
  ( ,,,,, 1 )
  (&)
  ;
)

So, let's say you parse it and replace ( ,,,,, n) with the n-th lambda
argument (not shown here let's call it X) followed by POST which is
defined as (which would probably require some delaying of POST) and
simplified with ")".

#define POST(...) __VA_ARGS__ POST_I WAIT_ID()(FLOP)
#define POST_I() )

(inline "EMPTY(" ) (,,,,, 1) (( const "EMPTY(" ) (,,,,, 1) (& );)
(inline "EMPTY(" ) X POST (( const "EMPTY(" ) X POST (& );)
(inline "EMPTY(" ) X ( const "EMPTY(" ) X & ")"; ")"

...and then FLOP followed by the removal of the outer parentheses:

(inline EMPTY( ) X ( const EMPTY( ) X & ) ; )

inline X ( const X & ) ;

Now, I'm not sure if this scales to every scenario. And it is playing
extremely fast and loose with hanging parenthesis. I haven't spent a
great deal of time playing with it. But, this would be magic:

#define _0 ARG(0)
#define _1 ARG(1)

APPLY(
    (A)(B),
    template<class T> class _0 : public _1 {
        public:
            inline _0(const _0& c) : _1(1, 2, 3) {
                // ...
            }
    };
)

#undef _0
#undef _1

If that can be made to work, that would be freakishly clever. You'd then
have to do lambda bindings as another type of "control flag" to delay them
(and hide the actual macros' names).

> As well as supporting lambda or even user-defined invokable expressions.
> Ultimately, bind would just be implemented as this(Yes I know chaos
> already has a bind macro):
>
>     #define CHAOS_PP_BIND(m, ...) (CHAOS_PP_BIND)(m, __VA_ARGS__)

The library already allows what you pass to be a deferred expression in
terms of NEXT(s). So you can put an arbitrary remapper:

#define MACRO(x, y) x - y
#define DROP_AND_SWAP(s, y, x) (x, y)

CHAOS_PP_EXPR(CHAOS_PP_ENUM(
    3, MACRO DROP_AND_SWAP, 3
))

...contrived, I know.
 
> I don't know what anyone else thinks of that, as well.

You could probably do something like that. However, depending on whatever
it is (say lambda), you usually want to parse the expression into a
quickly substitutable form once at the beginning.
 
>> http://chaos-pp.cvs.sourceforge.net/viewvc/chaos-pp/chaos-pp/built-docs/
>> primary.html
>>
>> There are a lot of bits of sample code throughout the documentation,
>> though the topical documentation incomplete.
>
> The documentation is really missing explanations on recursion steps, and
> parametric resumptions. I never really fully understood what those
> additional repetition macros did.

A lot of this type of stuff is extremely trickly subject matter. A
typical user isn't writing algorithms, etc.. They are just using the
provided higher-level macros. What Chaos' does, for the most part, is
take all implementation-level macros that could be useful as interfaces
and define them as interfaces. I agree, however, that over-arching
concepts, themes, idioms, and techniques need adequate documentation.

Where I got the term "parametric" from in this context I have no idea!
Essentially, those algorithms process through from (e.g.) EXPR_1 through
EXPR_10, but leave an extra EXPR_1, then jump back to EXPR_2, leave an
extra EXPR_2, and so on. I.e. they use the recursion backend like this:

X
X X
X X X
X X X
X X X
  X X
    X

I.e. it is sort of a backend linear multiplier.

The _X macros use the backend in an exponential fashion--but those have
some usability issues. I actually have a few combinations-of-both (i.e.
_PARAMETRIC_X--whatever that means) algorithms laying around somewhere
that are superior to the _X versions.

In both cases, however, these algorithms trade some speed for headroom.
_PARAMETRIC is a little slower than normal, and _X is slower yet.

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