Boost logo

Boost :

Subject: Re: [boost] Review Request: Variadic Macro Data library
From: Paul Mensonides (pmenso57_at_[hidden])
Date: 2011-02-21 03:57:07


On Sun, 20 Feb 2011 13:55:47 -0500, Edward Diener wrote:

> On 2/20/2011 12:19 PM, Paul Mensonides wrote:

>> All uses of macros are metaprogramming. The more programmers
>> understand that, the less "macro problems" there will be.
>
> A library presents an interface where one uses a macro to do
> "something". The programmer using that macro to accomplish some task in
> that library. The use of that macro is not "metaprogramming" as I
> understand it, although I think this is just an argument about a word
> meaning than any real disagreement. Imagine a relative newbie in C++
> using a macro and then being told he is "metaprogramming". No doubt he
> would feel a little proud of that fact <g>, but I doubt if that would
> prove much about his real "metaprogramming" knowledge or skills, not
> that I think "metaprogramming" is that abstruse.
>
> Undoubtedly a C++ programmer should understand macros. But whether he
> understands it at his own level and whether he understands it as you do
> are different things.

What I'm getting is actually what you mention before, mimicking function
calls. That even being a goal (or considered a good thing by accident)
rather than being benign (at best) is where things go wrong. Macros are
code generators (or, I suppose, code "removers"). They are not even
remotely tied to the syntax of the underlying language. When a macro
(that isn't a general purpose pp-lib macro) is defined, all too often the
author designs the macro such that its invocation appears to fit into the
syntax of the underlying language. For example, hacks such as using do/
while loops in order to require a semicolon outside the macro.

#define REGISTER(...) \
    do { \
        /* whatever */ \
    } while (false) \
    /**/

int main() {
    REGISTER(a, b, c); // <--
}

That point of view is evil.

Demanding that competent C++ programmers have a proper view of macros is
not demanding that they know all of the tricks that I or others know to
manipulate the preprocessor. However, it does require them to know that
macros are a fundamentally different thing with fundamentally different
mechanics (even if they don't understand those mechanics to the level of
detail that I or others do).

>> So, what's the problem with SOME_MACRO((a, b, c)) instead? I.e. a
>> tuple rather just variadic content?
>
> By the same token what is wrong with SOME_MACRO(a,b,c) ?

The interface is (potentially) silently broken when some other argument
needs to be passed. I.e. MACRO(a, b, c) => MACRO(id, a, b, c) is
potentially a silent break. MACRO((a, b, c)) => MACRO(id, (a, b, c)) is
not.

> The point is
> not that their is something wrong with SOME_MACRO((a,b,c)) or even with
> SOME_MACRO((a)(b)(c)) but that programmers using macros which a library
> provides seem more comfortable with SOME_MACRO(a,b,c). The reason for
> this is most probably because the latter mimics function call syntax and
> a library implementer is looking syntactically for as much sameness as
> possible in order to promote ease of use ( or memorability ).

Another way to provide comfort is via education. Hardcore pp-
metaprogramming knowledge is not required for this.

> One may just as well ask why C++ adapted variadic macros as part of the
> next proposed standard ? I believe it was solely for syntactic ease of
> use with a variable amount of macro tokens. It sure wasn't because C++
> provided much functionality to deal with the variadic data per se.

Actually, it is more likely just for the sake of compatibility with C. I
originally proposed adding them at a committee meeting in Redmond. That
proposal got rolled into a general "compatibility with C preprocessor"
proposal.

>> Believe it or not, I actually use preprocessor metaprogramming (as well
>> as template metaprogramming) quite heavily in my own code.
>
> Nah ! I don't believe it <g>.

Shocking, but true!

>> The only time
>> that I would provide a macro that uses the variadic data as an element
>> sequence (that's not a general-purpose pp-library macro) as an
>> interface is if it is absolutely certain that it will never become
>> higher-order and that the number of arguments before the variadic data
>> will never change.
>
> Fair enough. But I believe some other proposed Boost libraries, besides
> my own TTI library, are using variadic macro syntax in their public
> interface. Why should they not do that and take advantage of pp-lib at
> the same time ?

As I said before, I don't have a problem with these particular macros.
I.e. DATA_TO_SEQ(__VA_ARGS__). I only have a problem with ALGORITHM_B
(...) => ALGORITHM_A(DATA_TO_SEQ(__VA_ARGS__)). There are lots of other
uses of DATA_TO_SEQ (et al) and element extraction (ala TUPLE_ELEM)
besides this. As long as the pp-lib doesn't provide definitions like
ALGORITHM_B, I don't have a problem with it. If users provide that
themselves, in spite of a lack of endorsement for the library, the
ramifications are on their own heads.

The following are the chaos-pp analogs of the macros that you have in the
sandbox right now. If they were added to the pp-lib (where they belong
if they are worthwhile) these are the names that I would use (not because
I'm wedded to the names, but for symmetry with existing practice).

----
BOOST_VMD_DATA_SIZE(...)
Chaos has no direct analog of this ATM.  Normally the functionality is 
produced via CHAOS_PP_TUPLE_SIZE((...)).  However, if it existed it would 
be called CHAOS_PP_VARIADIC_SIZE(...).  If I (or you) added it to the pp-
lib, I would prefer it be called BOOST_PP_VARIADIC_SIZE(...) for symmetry 
with existing practice.
----
BOOST_VMD_DATA_ELEM(n, ...)
The direct analog of this in Chaos is CHAOS_PP_VARIADIC_ELEM(n, ...).  If 
I (or you) added it to the pp-lib, I would prefer it be called 
BOOST_PP_VARIADIC_SIZE(...).
----
BOOST_VMD_DATA_TO_PP_TUPLE(...)
Chaos has no direct analog of this because (as you know), it's pointless 
(unless you're doing something internally to for compiler workarounds).
-----
BOOST_VMD_DATA_TO_PP_{ARRAY,LIST,SEQ}(...)
Chaos has no direct analog for any of these because it doesn't consider 
variadic data to be a sequential data structure.  Chaos does have 
conversion macros to one data structure to another.  However, the basic 
functionality to do that is implemented by one generic algorithm:
    CHAOS_PP_CAST(CHAOS_PP_SEQ, (CHAOS_PP_TUPLE) (a, b, c))
        => (a)(b)(c)
    CHAOS_PP_CAST(CHAOS_PP_LIST, (CHAOS_PP_TUPLE) (a, b, c))
        => (a, (b, (c, ...)))
The library normally only supplies direct non-generic versions when there 
is a reasonable efficiency gain over the generic version or when there is 
an interesting implementation mechanic that I want to preserve.  There 
are a lot of these direct conversions for CHAOS_PP_SEQ and 
CHAOS_PP_TUPLE.  The current set is:
    CHAOS_PP_ARRAY_TO_LIST
    CHAOS_PP_ARRAY_TO_SEQ
    CHAOS_PP_ARRAY_TO_STRING
    CHAOS_PP_ARRAY_TO_TUPLE
    CHAOS_PP_SEQ_TO_ARRAY
    CHAOS_PP_SEQ_TO_LIST
    CHAOS_PP_SEQ_TO_STRING
    CHAOS_PP_SEQ_TO_TUPLE
    CHAOS_PP_TUPLE_TO_LIST
    CHAOS_PP_TUPLE_TO_SEQ
    CHAOS_PP_TUPLE_TO_STRING
The primary reason that there is no direct conversion (other than 
(__VA_ARGS__)) to the other data types is that with variadic data there 
is no such thing as an empty sequence and there is no safe way to define 
a rogue value for it without artificially limiting what the data can 
contain.
----
BOOST_VMD_PP_TUPLE_SIZE(tuple)
The direct analog of this in Chaos is CHAOS_PP_TUPLE_SIZE(tuple).  If I 
(or you) added this to the pp-lib, it should be just BOOST_PP_TUPLE_SIZE
(tuple).
----
BOOST_VMD_PP_TUPLE_ELEM(n, tuple)
The direct analog of this in Chaos is CHAOS_PP_TUPLE_ELEM(n, tuple) where 
it ignores the 'n' if variadics are enabled.
    CHAOS_PP_TUPLE_ELEM(?, 0, (a, b, c)) => a
If this was added to the pp-lib, I would prefer that it did so the same 
way.  However, another way would be "overload" the macro (which as no 
direct analog in the pp-lib or your library).  Basically, that ends up 
being something like:
    #if (variadics enabled)
    #define BOOST_PP_TUPLE_ELEM(...) \
        BOOST_PP_CAT( \
            BOOST_PP_TUPLE_ELEM_, \
            BOOST_PP_VARIADIC_SIZE(__VA_ARGS__) \
        )(__VA_ARGS__) \
        /**/
    #define BOOST_PP_TUPLE_ELEM_2(n, tuple) // ...
    #define BOOST_PP_TUPLE_ELEM_3(size, n, tuple) \
        BOOST_PP_TUPLE_ELEM_2(n, tuple) \
        /**/
    #else
    #define BOOST_PP_TUPLE_ELEM(size, n, tuple) // old way
    #endif
-----
BOOST_VMD_PP_TUPLE_REM_CTOR(tuple)
The direct analog of this in Chaos is CHAOS_PP_TUPLE_REM_CTOR (which, 
BTW, stands for "constructed parentheses removal").  The "constructed" 
refers to when the tuple is the direction result of a macro expansion.  
CHAOS_PP_REM_CTOR(?, MACRO()).  Otherwise, if you already have a tuple 
't', just use CHAOS_PP_REM t.  ATM, Chaos has CHAOS_PP_TUPLE_REM, 
CHAOS_PP_TUPLE_REM_CTOR, and CHAOS_PP_REM.  It does not have a 
CHAOS_PP_REM_CTOR.
----
BOOST_VMD_PP_TUPLE_{REVERSE,TO_{LIST,SEQ}}(tuple)
If added to the pp-lib, should be the same as BOOST_PP_TUPLE_ELEM above.  
Either ignored size argument or overload.
----
BOOST_VMD_PP_TUPLE_TO_DATA(tuple)
Isn't this just REM_CTOR?
----
BOOST_VMD_PP_{ARRAY,LIST,SEQ}_TO_DATA(ds)
These I don't see the point of these--particularly with the pp-lib 
because of compiler issues.  These already exist as BOOST_PP_LIST_ENUM 
and SEQ_ENUM.  There isn't an ARRAY_ENUM currently, but it's easy to 
implement.  The distinction between these names and the *_TO_DATA variety 
is that these are primary output macros.  If an attempt is made by a user 
to use the result as macro arguments, all of the issues with compilers 
(e.g. VC++) will be pulled into the user's domain.
----
To summarize what I would prefer:
BOOST_VMD_DATA_SIZE(...)
  -> BOOST_PP_VARIADIC_SIZE(...)
BOOST_VMD_DATA_ELEM(n, ...)
  -> BOOST_PP_VARIADIC_ELEM(n, ...)
BOOST_VMD_DATA_TO_PP_TUPLE(...)
  -> (nothing, unless workarounds are necessary)
BOOST_VMD_DATA_TO_PP_ARRAY(...)
  -> BOOST_PP_TUPLE_TO_ARRAY((...))
  or BOOST_PP_TUPLE_TO_ARRAY(size, (...))
BOOST_VMD_DATA_TO_PP_LIST(...)
  -> BOOST_PP_TUPLE_TO_LIST((...))
  or BOOST_PP_TUPLE_TO_LIST(size, (...))
BOOST_VMD_DATA_TO_PP_SEQ(...)
  -> BOOST_PP_TUPLE_TO_SEQ((...))
  or BOOST_PP_TUPLE_TO_SEQ(size, (...))
BOOST_VMD_PP_TUPLE_SIZE(tuple)
  -> BOOST_PP_TUPLE_SIZE(tuple)
  or BOOST_PP_TUPLE_SIZE(size, tuple)
BOOST_VMD_PP_TUPLE_ELEM(n, tuple)
  -> BOOST_PP_TUPLE_ELEM(n, tuple)
  or BOOST_PP_TUPLE_ELEM(size, n, tuple)
BOOST_VMD_PP_TUPLE_REM_CTOR(tuple)
  -> BOOST_PP_TUPLE_REM_CTOR(tuple)
  or BOOST_PP_TUPLE_REM_CTOR(size, tuple)
BOOST_VMD_PP_TUPLE_REVERSE(tuple)
  -> BOOST_PP_TUPLE_REVERSE(tuple)
  or BOOST_PP_TUPLE_REVERSE(size, tuple)
BOOST_VMD_PP_TUPLE_TO_LIST(tuple)
  -> BOOST_PP_TUPLE_TO_LIST(tuple)
  or BOOST_PP_TUPLE_TO_LIST(size, tuple)
BOOST_VMD_PP_TUPLE_TO_SEQ(tuple)
  -> BOOST_PP_TUPLE_TO_SEQ(tuple)
  or BOOST_PP_TUPLE_TO_SEQ(size, tuple)
BOOST_VMD_PP_TUPLE_TO_DATA(tuple)
  -> BOOST_PP_TUPLE_REM_CTOR(tuple)
  or BOOST_PP_TUPLE_REM_CTOR(size, tuple)
BOOST_VMD_PP_ARRAY_TO_DATA(array)
  -> BOOST_PP_ARRAY_ENUM(array)
BOOST_VMD_PP_LIST_TO_DATA(list)
  -> BOOST_PP_LIST_ENUM(list)
BOOST_VMD_PP_SEQ_TO_DATA(seq)
  -> BOOST_PP_SEQ_ENUM(seq)
also add:
  BOOST_PP_REM, BOOST_PP_EAT
The basic gist is to add the low-level variadic stuff and adapt the 
existing tuple stuff to not require the size.
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