27

I understand C11 generics for one-parameter functions, like this: (from here)

#define acos(X) _Generic((X), \
    long double complex: cacosl, \
    double complex: cacos, \
    float complex: cacosf, \
    long double: acosl, \
    float: acosf, \
    default: acos \
    )(X)

But, it seems to be a pain for functions with two arguments, you need to nest calls to _Generic, which is really ugly; Excerpt from the same blog:

#define pow(x, y) _Generic((x), \
long double complex: cpowl, \

double complex: _Generic((y), \
long double complex: cpowl, \
default: cpow), \

float complex: _Generic((y), \
long double complex: cpowl, \
double complex: cpow, \
default: cpowf), \

long double: _Generic((y), \
long double complex: cpowl, \
double complex: cpow, \
float complex: cpowf, \
default: powl), \

default: _Generic((y), \
long double complex: cpowl, \
double complex: cpow, \
float complex: cpowf, \
long double: powl, \
default: pow), \

float: _Generic((y), \
long double complex: cpowl, \
double complex: cpow, \
float complex: cpowf, \
long double: powl, \
float: powf, \
default: pow) \
)(x, y)

Is there a way to have more human being-readable generics for multiparameter functions, like this for instance :

#define plop(a,b) _Generic((a,b), \
      (int,long): plopii, \
      (double,short int): plopdd)(a,b)

Thanks in advance for your replies. The basic idea would be having a macro wrapper for _Generic.

Mathuin
  • 790
  • 1
  • 6
  • 13
  • 1
    For reference: 6.5.1.1 Generic selection. According to this, the first argument of `_Generic` is an `assignment-expression` whose type determines which element of the list of expressions (like `cpowl`, `cpow` etc.) is chosen. So it's not possible to use a single `_Generic` selection to select based on two types / two expressions. – dyp Jun 25 '13 at 17:12
  • 1
    @DyP it doesn't mean there are no workarounds. – Elazar Jun 25 '13 at 17:14
  • @Elazar Indeed, that's why that isn't an answer ;) But the point holds that it isn't possible using a *single* generic selection. – dyp Jun 25 '13 at 17:14
  • I know that, I'm looking for a clever macro which would allow me to use type selection in a "nicer" way. i'll edit my post, it's not clear enough. – Mathuin Jun 25 '13 at 17:16
  • What about using _generics with multi-param functions, but you only want to switch on the first? I'm having a hell of a time trying to figure out the syntax for this, do you use `#define Power2Mask(BitOrder,Bits2Mask) _Generic((BitOrder) ... ) (Bits2Mask)` or `#define Power2Mask(BitOrder,Bits2Mask) _Generic((BitOrder,Bits2Mask) ... ) (BitOrder,Bits2Mask)`? Clang is not helping debug this at all – MarcusJ Jul 29 '17 at 17:40

5 Answers5

18

Given that the controlling expression of _Generic is not evaluated, I'd suggested applying some arithmetic operation that does the appropriate type-combining, and switching on the result. Thus:

#define OP(x, y) _Generic((x) + (y), \
    long double complex: LDC_OP(x, y), \
    double complex: DC_OP(x, y), \
    ... )

Of course this only works for certain cases, but you can always expand out those for which the "collapsed" type is not helpful. (This would let one take care of array-N-of-char vs char *, for instance, as with the linked printnl example, and then if the combined type is int, one can go back and check for char and short.)

torek
  • 448,244
  • 59
  • 642
  • 775
  • 1
    Not only it will work only for certain cases, it will be very subtle and error-prone - you will need to know the conversion rules in C by heart. – Elazar Jun 25 '13 at 17:34
  • 3
    @Elazar: sure, but if you're writing generics, you'd *better* know that, or have a cheat-sheet of all possible type combinations, while you're writing. :-) – torek Jun 25 '13 at 17:35
  • I found this answer in another question on SO, but it didn't fit my needs as it's "dangerous" as Elazar points out. +1 though. – Mathuin Jun 25 '13 at 17:36
  • Not dangerous nor subtle - direct and simple. – chux - Reinstate Monica Mar 12 '20 at 19:34
15

Since C doesn't have tuples, let's make our own tuples:

typedef struct {int _;} T_double_double;
typedef struct {int _;} T_double_int;
typedef struct {int _;} T_int_double;
typedef struct {int _;} T_int_int;

typedef struct { T_double_double Double; T_double_int Int;} T_double;
typedef struct { T_int_double Double;    T_int_int    Int;} T_int;

#define typeof1(X)       \
_Generic( (X),            \
    int:    (T_int){{0}},  \
    double: (T_double){{0}} )

#define typeof2(X, Y)      \
_Generic( (Y),              \
    int:    typeof1(X).Int,  \
    double: typeof1(X).Double )

This is the client code:

#include <stdio.h>
#include "generics.h"

#define typename(X, Y)               \
_Generic( typeof2(X, Y),              \
    T_int_int: "int, int\n",           \
    T_int_double: "int, double\n",      \
    T_double_double: "double, double\n", \
    T_double_int: "double, int\n",        \
    default: "Something else\n"            )

int main() {
    printf(typename(1, 2));
    printf(typename(1, 2.0));
    printf(typename(1.0, 2.0));
    printf(typename(1.0, 2));
    return 0;
}

And it works:

~/workspace$ clang -Wall -std=c11 temp.c
~/workspace$ ./a.out 
int, int
int, double
double, double
double, int

Yes, you will still need to write code in an exponential size. But at least you will be able to reuse it.

Elazar
  • 20,415
  • 4
  • 46
  • 67
  • i'm trying to understand your code, but it seems nicer to me even if it's unclear (the `(*((types_unit*)0))` part).I will wait a little before marking the question as resolved, I like having multiple points of view. – Mathuin Jun 25 '13 at 18:45
  • it is an expression of type `types_unit`. That's what typeofX macros need. – Elazar Jun 25 '13 at 18:56
  • Couldn't it be done with an enum ? md5 pointed me that on IRC – Mathuin Jun 25 '13 at 19:00
  • I can't see how an enum can help here. – Elazar Jun 25 '13 at 19:13
  • 1
    That's quite something. The ice is slightly thin with the `*(T *)0` trick but I've never seen a system where this does not work, and it's normally used to implement `offsetof`. @Mathuin: `enum` types can be used at the bottom level but it just makes things worse as you then have to supply some unique-yet-unused enumeration constant(s) for each one. Best to stick with `struct` (or `union`). – torek Jun 25 '13 at 21:44
  • @torek I don't think this is UB, since I don't *really* dereference it - I use it only as a switch. If that's a problem, any number other than 0 would do just as well. I am looking for a way to generalize it but it seems to be non trivial. – Elazar Jun 25 '13 at 21:54
  • 1
    The "thin ice" remark dates all the way back to the original 1989 C standard. Someone (I have no idea who) pointed out that `offsetof` can be done with `(char *)&((struct foo *)0)->member - (char *)0`, which also does not really follow any null pointers, but the committee members were nervous about declaring that to be OK. I don't know why, it's trivial to tell compiler writers "must make this work" and it's not hard for them/us to make it work (compile-time constants must already be detected at, well, "compile" time...). – torek Jun 25 '13 at 23:34
  • 1
    @torek "Someone (I have no idea who)" -- I did, for one. I remember Bill Plauger questioning whether `offsetof` had any utility, and I pointed out that there's a common usage (structs ending with resizable arrays) and that extant code used that definition to implement it. But I couldn't have been the first ... the formal request for the feature surely provided that definition. – Jim Balter Jun 27 '13 at 22:16
  • @JimBalter: we also used the moral equivalent of `offsetof` (and I changed it to actual `offsetof`s) in the kernel, defining symbols for assembly code. – torek Jun 27 '13 at 23:50
  • @torek By "the kernel" I would guess you meant BSD, but with you it could be any of them. :-) – Jim Balter Jun 27 '13 at 23:54
  • @JimBalter Correct me if I'm wrong, but doesn't `offsetof` *require* the value to be zero, in order to be portable? This is not the case here; 0 is just a valid expression. – Elazar Jun 28 '13 at 07:29
  • 2
    @Elazar `offsetof` is provided by the implementation. so portability isn't relevant. Implementation of `offsetof` isn't specified by the standard; it just has to work. – Jim Balter Jun 28 '13 at 07:53
  • @JimBalter I referred to `(char *)&((struct foo *)0)->member - (char *)0` – Elazar Jun 28 '13 at 08:05
  • @Elazar I know what you referred to. You don't seem to understand what I wrote, but I won't try again. – Jim Balter Jun 28 '13 at 08:07
  • Well I hope @torek will be kind enough to help me understand that. Are there no issues of portability in standard library implementations? or am I missing something else? I'd thought implementing a macro in a way that will "just work" for any target is a good idea. – Elazar Jun 28 '13 at 08:19
  • 2
    @Elazar: I'm not sure what you're referring to. If you're an implementor writing a library, you can choose to write non-portable code for each target, or portable code, or a mix of both. I always just do whatever "seems best" at the time, generally "completely portable if easy, but if that's too hard, good-enough-for-my-purposes, and if *that*'s too hard, specific-to-this-target." – torek Jun 28 '13 at 08:35
  • Come to think about it, the zero is not needed here at all (unlike in offsetof()). What is needed is some legal address. – Elazar Dec 05 '13 at 09:27
  • Or you can write a parser that generates this code from a more human-friendly syntax. – étale-cohomology Jul 07 '21 at 11:52
7

I really feel like the above solutions are not much easier or cleaner than the OP's original implementation. I think the best approach is to keep it simple and just abstract macros with more macros. The following is an example.

#include<stdio.h>

double multiply_id ( int a, double b )
{
    return a * b;
}

double multiply_di ( double a, int b )
{
    return a * b;
}

double multiply_dd ( double a, double b )
{
    return a * b;
}

int multiply_ii ( int a, int b )
{
    return a * b;
}


/*
#define multiply(a,b) _Generic((a), \
int: _Generic((b), \
    int: multiply_ii, \
    double: multiply_id), \
double: _Generic((b), \
    int: multiply_di, \
    double: multiply_dd) ) (a,b)
*/

#define _G2(ParamB,ParamA_Type, TypeB1, TypeB1_Func, TypeB2, TypeB2_Func) \
    ParamA_Type: _Generic((ParamB), \
        TypeB1: TypeB1_Func, \
        TypeB2: TypeB2_Func)

#define multiply(a,b) _Generic((a), \
    _G2(b,int,int,multiply_ii,double,multiply_id), \
    _G2(b,double,int,multiply_di,double,multiply_dd) ) (a,b)


int main(int argc, const char * argv[]) {
    int i;
    double d;

    i = 5;
    d = 5.5;

    d = multiply( multiply(d, multiply(d,i) ) ,multiply(i,i) );

    printf("%f\n", d);  
    return 0;
}

_G2 is a macro for two parameter generics. This could be extended to a _G3 or more quite easily. The trick is to just do it normally, then build a macro from it's form.

6

Here's a version that only requires you to write a linear amount of code by hand, all of which is directly related to the matter at hand (no large trees of hand-defined types). First, usage example:

#include <stdio.h>

// implementations of print
void print_ii(int a, int b) { printf("int, int\n"); }
void print_id(int a, double b) { printf("int, double\n"); }
void print_di(double a, int b) { printf("double, int\n"); }
void print_dd(double a, double b) { printf("double, double\n"); }
void print_iii(int a, int b, int c) { printf("int, int, int\n"); }
void print_default(void) { printf("unknown arguments\n"); }

// declare as overloaded
#define print(...) OVERLOAD(print, (__VA_ARGS__), \
    (print_ii, (int, int)), \
    (print_id, (int, double)), \
    (print_di, (double, int)), \
    (print_dd, (double, double)), \
    (print_iii, (int, int, int)) \
)


#define OVERLOAD_ARG_TYPES (int, double)
#define OVERLOAD_FUNCTIONS (print)


#include "activate-overloads.h"


int main(void) {
    print(44, 47);   // prints "int, int"
    print(4.4, 47);  // prints "double, int"
    print(1, 2, 3);  // prints "int, int, int"
    print("");       // prints "unknown arguments"
}

This is probably the lightest syntax you're going to get for this.

Now for the disadvantages/restrictions:

  • you need to declare all of the argument types for the overloaded functions in the list OVERLOADED_ARG_TYPES
  • argument types must be one word names (not a huge problem, thanks to typedef, but needs to be remembered)
  • this causes huge code bloat at the actual call site (although it's easy bloat for a compiler to optimize away - GCC will at -O1)
  • relies on a massive PP library (see below)

You must also define a X_default function which takes no arguments; don't add this to the overload declaration block. This is used for non-matches (if you want to call it directly, call the overload with any non-matching value, like a compound literal anonymous struct or something).

Here's activate-overloads.h:

// activate-overloads.h
#include <order/interpreter.h>

#define ORDER_PP_DEF_8dispatch_overload ORDER_PP_FN( \
8fn(8N, 8V, \
    8do( \
        8print( 8cat(8(static inline int DISPATCH_OVER_), 8N) ((int ac, int av[]) { return ) ), \
        8seq_for_each_with_idx( \
            8fn(8I, 8T, \
                8let( (8S, 8tuple_to_seq(8tuple_at_1(8T))), \
                    8print( 8lparen (ac==) 8to_lit(8seq_size(8S)) ), \
                    8seq_for_each_with_idx(8fn(8I, 8T, 8print( (&&av[) 8I (]==) 8cat(8(K_), 8T) )), 0, 8S), \
                    8print( 8rparen (?) 8I (:) ) \
                )), \
            1, 8V), \
        8print( ( -1; }) ) \
    ) ))

#define TYPES_TO_ENUMS(TS) ORDER_PP ( \
    8do( \
        8seq_for_each(8fn(8T, 8print( 8T (:) 8cat(8(K_), 8T) (,) )), \
                      8tuple_to_seq(8(TS))), \
        8print( (default: -1) ) \
    ) \
)
#define ENUMERATE_TYPES(TS) enum OVERLOAD_TYPEK { ORDER_PP ( \
    8seq_for_each(8fn(8V, 8print( 8V (,) )), 8types_to_vals(8tuple_to_seq(8(TS)))) \
) };
#define ORDER_PP_DEF_8types_to_vals ORDER_PP_FN( \
8fn(8S, 8seq_map(8fn(8T, 8cat(8(K_), 8T)), 8S)) )


ENUMERATE_TYPES(OVERLOAD_ARG_TYPES)
#define OVER_ARG_TYPE(V) _Generic((V), TYPES_TO_ENUMS(OVERLOAD_ARG_TYPES) )

#define OVERLOAD
ORDER_PP (
    8seq_for_each(
        8fn(8F,
            8lets( (8D, 8expand(8adjoin( 8F, 8(()) )))
                   (8O, 8seq_drop(2, 8tuple_to_seq(8D))),
                8dispatch_overload(8F, 8O) )),
        8tuple_to_seq(8(OVERLOAD_FUNCTIONS))
    )
)
#undef OVERLOAD

#define OVERLOAD(N, ARGS, ...) ORDER_PP ( \
    8do( \
        8print(8lparen), \
        8seq_for_each_with_idx( \
            8fn(8I, 8T, \
                8lets( (8S, 8tuple_to_seq(8tuple_at_1(8T))) \
                       (8R, 8tuple_to_seq(8(ARGS))) \
                       (8N, 8tuple_at_0(8T)), \
                    8if(8equal(8seq_size(8S), 8seq_size(8R)), \
                        8do( \
                            8print( 8lparen (DISPATCH_OVER_##N) 8lparen 8to_lit(8seq_size(8R)) (,(int[]){) ), \
                            8seq_for_each(8fn(8A, 8print( (OVER_ARG_TYPE) 8lparen 8A 8rparen (,) )), 8R), \
                            8print( (-1}) 8rparen (==) 8I 8rparen (?) 8N 8lparen ), \
                            8let( (8P, 8fn(8A, 8T, \
                                           8print( (_Generic) 8lparen 8lparen 8A 8rparen (,) 8T (:) 8A (,default:*) 8lparen 8T (*) 8rparen (0) 8rparen ) \
                                           )), \
                                8ap(8P, 8seq_head(8R), 8seq_head(8S)), \
                                8seq_pair_with(8fn(8A, 8T, 8do(8print((,)), 8ap(8P, 8A, 8T))), 8seq_tail(8R), 8seq_tail(8S)) \
                            ), \
                            8print( 8rparen (:) ) \
                        ), \
                        8print(( )) ) \
                )), \
            1, 8tuple_to_seq(8((__VA_ARGS__))) \
        ), \
        8print( 8cat(8(N), 8(_default)) (()) 8rparen) \
    ) \
)

This requires the awesome Order preprocessor library by Vesa K.

How it actually works: the OVERLOAD_ARG_TYPES declaration is used to build an enum listing all of the argument types in use as constants. Every call to the overloaded name can then be replaced in the caller code by a big ternary operation dispatching between all implementations (of the right argument number). The dispatch works by using _Generic to generate enum values from the types of the arguments, put these in an array, and have an automatically-generated dispatcher function return the ID (position in the original block) for that type combination. If the ID matches, the function is called. If the arguments are of the wrong type, dummy values are generated for the unused call to an implementation to avoid a type mismatch.

Technically this involves a "runtime" dispatch, but since every type ID is constant and the dispatcher function is static inline, the whole thing should be easy for a compiler to optimize out, except for the wanted call (and GCC does indeed optimize it all away).

This is a refinement of the technique previously posted here (same idea, now with pretty and ultra-light syntax).

Community
  • 1
  • 1
Alex Celeste
  • 12,824
  • 10
  • 46
  • 89
3

Oh well... here's the beginning of a macro solution using the boost preprocessor library (C99-preprocessor-compliant).

The idea was to provide a generic syntax that allows writing nested generic selections for an arbitrary number of arguments. To keep it "simple", the expression to select is the same for all elements on the same level of selection (you could define another syntax to alter the controlling expression on each selection of a level individually..).


This example from OP

#define plop(a,b) _Generic((a,b), \
  (int,long): plopii, \
  (double,short int): plopdd)(a,b)

becomes

#define plop(a,b)                  \
  MULT_GENERIC((a,b),              \
    (int, (long, plopii)),         \
    (double, (short int, plopdd))  \
  )(a,b)

Although I guess one could alter it slightly to get something like:

#define plop(a,b)                  \
  MULT_GENERIC((a,b),              \
    (int, long: plopii),           \
    (double, short int: plopdd)    \
  )(a,b)

Which could expand for three parameters to:

#define plop(a,b,c)                                \
  MULT_GENERIC((a,b,c),                            \
    (int, (double, long: plopidl, int: plopidi)),  \
    (double, (short int, long: plopdsl))           \
  )(a,b)

A further comment: I think OP's syntax could be done as well, but it isn't as flexible, as you have to repeat the first argument for every possible second argument, e.g.

#define plop(a,b) _Generic((a,b), \
  (int,long): plopii, \
  (int,double): plobid \
  (double,short int): plopdd)(a,b)

OP's example in my syntax. Note that you don't gain much here as you still have to specify each type specifically and in this case the second type several times for different first types.

#define pow(x, y) MULT_GENERIC(                        \
        (x, y),                                        \
        (long double complex, (default, cpowl)         \
        ),                                             \
        (double complex, (long double complex, cpowl)  \
                       , (default, cpow)               \
        ),                                             \
        (float complex, (long double complex, cpowl)   \
                      , (double complex, cpow)         \
                      , (default, cpowf)               \
        ),                                             \
        (long double, (long double complex, cpowl)     \
                    , (double complex, cpow)           \
                    , (float complex, cpowf)           \
                    , (default, powl)                  \
        ),                                             \
        (default, (long double complex, cpowl)         \
                , (double complex, cpow)               \
                , (float complex, cpowf)               \
                , (long double, powl)                  \
                , (default, pow)                       \
         ),                                            \
         (float, (long double complex, cpowl)          \
               , (double complex, cpow)                \
               , (float complex, cpowf)                \
               , (long double, powl)                   \
               , (float, powf)                         \
               , (default, pow)                        \
         )                                             \
    )                                                  \
    (x, y)

pow(x, y)

This is resolved to:

_Generic( (x), long double complex : _Generic( (y), default : cpowl ) , double complex : _Generic( (y), long double complex : cpowl , default : cpow ) , float complex : _Generic( (y), long double complex : cpowl , double complex : cpow , default : cpowf ) , long double : _Generic( (y), long double complex : cpowl , double complex : cpow , float complex : cpowf , default : powl ) , default : _Generic( (y), long double complex : cpowl , double complex : cpow , float complex : cpowf , long double : powl , default : pow ) , float : _Generic( (y), long double complex : cpowl , double complex : cpow , float complex : cpowf , long double : powl , float : powf , default : pow ) ) (x, y)

Which is, reformatted:

_Generic((x),
  long double complex: _Generic((y), default: cpowl)
, double complex: _Generic((y),
                             long double complex: cpowl
                           , default: cpow)
, float complex: _Generic((y),
                            long double complex: cpowl
                          , double complex: cpow
                          , default: cpowf)
, long double: _Generic((y),
                          long double complex: cpowl
                        , double complex: cpow
                        , float complex: cpowf
                        , default: powl)
, default: _Generic((y),
                      long double complex: cpowl
                    , double complex: cpow
                    , float complex: cpowf
                    , long double: powl
                    , default: pow)
, float: _Generic((y)
                  , long double complex: cpowl
                  , double complex: cpow
                  , float complex: cpowf
                  , long double: powl
                  , float : powf
                  , default: pow)
)
(x, y)

Because of the recursive nature, I had to introduce copies of macros; this solution also needs a clean-up (I'm a bit tired). The macros:

#include <boost/preprocessor.hpp>

#define MULT_GENERIC_GET_ASSOC_SEQ(DATA_TUPLE) \
    BOOST_PP_TUPLE_ELEM(2, DATA_TUPLE)

#define MULT_GENERIC_NTH_ASSOC_TUPLE(N, DATA_TUPLE) \
    BOOST_PP_SEQ_ELEM( N, MULT_GENERIC_GET_ASSOC_SEQ(DATA_TUPLE) )

#define MULT_GENERIC_GET_TYPENAME(N, DATA_TUPLE) \
    BOOST_PP_TUPLE_ELEM(0, MULT_GENERIC_NTH_ASSOC_TUPLE(N, DATA_TUPLE))

#define MULT_GENERIC_GET_EXPR( N, DATA_TUPLE ) \
    BOOST_PP_TUPLE_ELEM(1, MULT_GENERIC_NTH_ASSOC_TUPLE(N, DATA_TUPLE))




#define MULT_GENERIC_LEVEL_REP1(z, N, DATA_TUPLE) \
    MULT_GENERIC_GET_TYPENAME( N, DATA_TUPLE ) \
    : \
    BOOST_PP_TUPLE_ELEM(1, DATA_TUPLE) /*LEVEL_MACRO*/ (       \
          BOOST_PP_TUPLE_ELEM(0, DATA_TUPLE) /*SEL_EXPR_SEQ*/    \
        , BOOST_PP_SEQ_POP_FRONT( BOOST_PP_TUPLE_TO_SEQ(MULT_GENERIC_NTH_ASSOC_TUPLE(N, DATA_TUPLE)) )    \
        )

#define MULT_GENERIC_LEVEL1(SEL_EXPR_SEQ, LEVEL_MACRO, ASSOC_SEQ) \
    _Generic(                   \
        (BOOST_PP_SEQ_HEAD(SEL_EXPR_SEQ)),                   \
        BOOST_PP_ENUM( BOOST_PP_SEQ_SIZE(ASSOC_SEQ), MULT_GENERIC_LEVEL_REP1, (BOOST_PP_SEQ_POP_FRONT(SEL_EXPR_SEQ), LEVEL_MACRO, ASSOC_SEQ) ) \
    )

#define MULT_GENERIC_LEVEL_REP2(z, N, DATA_TUPLE) \
    MULT_GENERIC_GET_TYPENAME( N, DATA_TUPLE ) \
    : \
    BOOST_PP_TUPLE_ELEM(1, DATA_TUPLE) /*LEVEL_MACRO*/ (       \
          BOOST_PP_TUPLE_ELEM(0, DATA_TUPLE) /*SEL_EXPR_SEQ*/    \
        , BOOST_PP_SEQ_POP_FRONT( BOOST_PP_TUPLE_TO_SEQ(MULT_GENERIC_NTH_ASSOC_TUPLE(N, DATA_TUPLE)) )    \
        )

#define MULT_GENERIC_LEVEL2(SEL_EXPR_SEQ, LEVEL_MACRO, ASSOC_SEQ) \
    _Generic(                   \
        (BOOST_PP_SEQ_HEAD(SEL_EXPR_SEQ)),                   \
        BOOST_PP_ENUM( BOOST_PP_SEQ_SIZE(ASSOC_SEQ), MULT_GENERIC_LEVEL_REP2, (BOOST_PP_SEQ_POP_FRONT(SEL_EXPR_SEQ), LEVEL_MACRO, ASSOC_SEQ) ) \
    )




#define MULT_GENERIC0(SEL_EXPR_SEQ, ASSOC_SEQ) \
    BOOST_PP_SEQ_HEAD(ASSOC_SEQ)

#define MULT_GENERIC1(SEL_EXPR_SEQ, ASSOC_SEQ) \
    MULT_GENERIC_LEVEL1( SEL_EXPR_SEQ, MULT_GENERIC0, ASSOC_SEQ )

#define MULT_GENERIC2(SEL_EXPR_SEQ, ASSOC_SEQ) \
    MULT_GENERIC_LEVEL2( SEL_EXPR_SEQ, MULT_GENERIC1, ASSOC_SEQ )

#define MULT_GENERIC(SEL_EXPR_TUPLE, ...) \
    BOOST_PP_CAT(MULT_GENERIC, BOOST_PP_TUPLE_SIZE(SEL_EXPR_TUPLE)) ( BOOST_PP_TUPLE_TO_SEQ(SEL_EXPR_TUPLE), BOOST_PP_TUPLE_TO_SEQ((__VA_ARGS__)) )
dyp
  • 38,334
  • 13
  • 112
  • 177