Variadac Macro apply macro to all arguments

2019-07-02 08:09发布

问题:

I was experimenting with C++11 variadac macros.

I was trying to apply another macro to each argument in the list.
This is my first try:

#define  APPLY_CHAIN(first, ...)    APPLY_ACT(first) APPLY_CHAIN( __VA_ARGS__ )

Unfortunately this did not work.

I eventually got it to work. But it was a bit convoluted and has a limit of 'n' (where 'n' is a max size that I am willing to type macros for).

#define COUNT_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...)    N
#define COUNT(...)   COUNT_N( __VA_ARGS__, 10, 9, 8, 7, 6, 5 ,4, 3, 2, 1)

#define BUILD_CHAIN_1(_1)       APPLY_ACT(_1)
#define BUILD_CHAIN_2(_1, ...)  APPLY_ACT(_1) BUILD_CHAIN_1(__VA_ARGS__)
#define BUILD_CHAIN_3(_1, ...)  APPLY_ACT(_1) BUILD_CHAIN_2(__VA_ARGS__)
#define BUILD_CHAIN_4(_1, ...)  APPLY_ACT(_1) BUILD_CHAIN_3(__VA_ARGS__)
#define BUILD_CHAIN_5(_1, ...)  APPLY_ACT(_1) BUILD_CHAIN_4(__VA_ARGS__)
#define BUILD_CHAIN_6(_1, ...)  APPLY_ACT(_1) BUILD_CHAIN_5(__VA_ARGS__)
#define BUILD_CHAIN_7(_1, ...)  APPLY_ACT(_1) BUILD_CHAIN_6(__VA_ARGS__)
#define BUILD_CHAIN_8(_1, ...)  APPLY_ACT(_1) BUILD_CHAIN_7(__VA_ARGS__)
#define BUILD_CHAIN_9(_1, ...)  APPLY_ACT(_1) BUILD_CHAIN_8(__VA_ARGS__)
#define BUILD_CHAIN_10(_1,...)  APPLY_ACT(_1) BUILD_CHAIN_9(__VA_ARGS__)


#define BUILD_CHAIN_INC( CT, ...)   BUILD_CHAIN_ ## CT (__VA_ARGS__)
#define BUILD_CHAIN( CT, ...)       BUILD_CHAIN_INC(CT, __VA_ARGS__)
#define APPLY_CHAIN(...)            BUILD_CHAIN( COUNT(__VA_ARGS__), __VA_ARGS__)

#define APPLY_ACT(X)                #X

#include <iostream>

int main()
{
    std::cout << APPLY_CHAIN(1,2,3,4,5) << "\n";
}

So my question is: Am I missing something simple that would allow me iterate along the argument chain?

回答1:

It would appear that recursive macros are still not possible, since the preprocessor only takes one pass over the source.

I bet you could make an explicit looping construct using a dummy #include using a conditional guard (which is perhaps what boost.preprocessor ends up doing.)



回答2:

Your solution to the argument count problem is the same as the one posted to [comp.lang.c] (IIRC) by Laurent Deniau in January 2006.

Modified to work also with Visual C++, his argument counting looks like this:

#pragma once

/*
* The PP_NARG macro evaluates to the number of arguments that have been
* passed to it.
*
* Laurent Deniau, "__VA_NARG__," 17 January 2006, <comp.std.c> (29 November 2007).
* https://groups.google.com/forum/?fromgroups=#!topic/comp.std.c/d-6Mj5Lko_s
*/

// Added workaround for silly MSVC bug that yields "too few parameters" warning.
// - Alf
#include <progrock/cppx/macro/invoke.h>         // CPPX_INVOKE
#include <progrock/cppx/macro/concat.h>         // CPPX_CONCAT

#define PP_ARG_N( \
_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
_41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
_61,_62,_63,N,...) N

#define PP_RSEQ_N() \
63,62,61,60, \
59,58,57,56,55,54,53,52,51,50, \
49,48,47,46,45,44,43,42,41,40, \
39,38,37,36,35,34,33,32,31,30, \
29,28,27,26,25,24,23,22,21,20, \
19,18,17,16,15,14,13,12,11,10, \
9,8,7,6,5,4,3,2,1,0

#if 0
    #define PP_NARG_(...) PP_ARG_N( __VA_ARGS__ )
    #define PP_NARG( ...) PP_NARG_( __VA_ARGS__, PP_RSEQ_N() )
#else
    #define PP_NARG_(...) CPPX_INVOKE( PP_ARG_N, (__VA_ARGS__) )
    #define PP_NARG(...) PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#endif

The CPPX_CONCAT macro:

#pragma once

#define CPPX_CONCAT__( a, b )       a ## b
#define CPPX_CONCAT_( a, b )        CPPX_CONCAT__( a, b )
#define CPPX_CONCAT( a, b )         CPPX_CONCAT_( a, b )

The CPPX_INVOKE macro:

#pragma once

#define CPPX_INVOKE( macro, args ) macro args

Example usage:

#pragma once
#include <progrock/cppx/macro/nargs.h>          // CPPX_NARGS, CPPX_CONCAT

#define CPPX_IS_UNUSED_( name ) \
    (void) name; struct name

#define CPPX_IS_UNUSED_1( a ) \
    CPPX_IS_UNUSED_( a )

#define CPPX_IS_UNUSED_2( a, b ) \
    CPPX_IS_UNUSED_1( a ); CPPX_IS_UNUSED_( b )

#define CPPX_IS_UNUSED_3( a, b, c ) \
    CPPX_IS_UNUSED_2( a, b ); CPPX_IS_UNUSED_( c )

#define CPPX_IS_UNUSED_4( a, b, c, d ) \
    CPPX_IS_UNUSED_3( a, b, c ); CPPX_IS_UNUSED_( d )

#define CPPX_IS_UNUSED_5( a, b, c, d, e ) \
    CPPX_IS_UNUSED_4( a, b, c, d ); CPPX_IS_UNUSED_( e )

#define CPPX_IS_UNUSED( ... )      \
    CPPX_INVOKE( CPPX_CONCAT( CPPX_IS_UNUSED_, CPPX_NARGS( __VA_ARGS__ ) ), ( __VA_ARGS__ ) )

#define CPPX_IS_INTENTIONALLY_UNUSED    CPPX_IS_UNUSED

As this exemplifies the main clue is to not use recursion (at least not directly), but instead manual code repetition for each number of arguments.

I believe that recursive macros are not formally possible. However, few compilers are conforming in this area, and some years ago at least you could indeed cajole WIndows compilers into doing recursive macro substitution. As I recall that was the technique originally used in the code that eventually became the Boost Preprocessor Library (now it uses formally valid code, by centralizing a big list of number-of-arguments-specific handlers, except workarounds for compiler-specific quirks).

That code repetition can most probably be automated via the Boost Preprocessor library, but then you'd use Boost also for the other stuff (presumably the point is to avoid dependency on a large library like Boost, at least that was my motivation when I wrote the above).

Disclaimer: I didn't retry this code before posting and I can't remember whether it was in a state of being modified, or what. But it does show the general principles.



回答3:

Look at Boost.Preprocessor. Specifically BOOST_PP_SEQ_FOLD_LEFT. You'll need BOOST_PP_VARIADIC_TO_SEQ.

Something like

#include <boost/preprocessor.hpp>
#include <iostream>

#define OP(d, state, x) state BOOST_PP_STRINGIZE(x)
#define APPLY_CHAIN(...) BOOST_PP_SEQ_FOLD_LEFT( OP, BOOST_PP_EMPTY() , BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__) ) 

int main() {
  std::cout <<  APPLY_CHAIN(1,2,3,4,5) << std::endl;
}
//expands to   std::cout << "1" "2" "3" "4" "5" << std::endl;