29

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.

5
  • 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. Commented Jun 25, 2013 at 17:12
  • 1
    @DyP it doesn't mean there are no workarounds. Commented Jun 25, 2013 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. Commented Jun 25, 2013 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. Commented Jun 25, 2013 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 Commented Jul 29, 2017 at 17:40

5 Answers 5

20

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.)

Sign up to request clarification or add additional context in comments.

4 Comments

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: 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. :-)
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.
Not dangerous nor subtle - direct and simple.
16

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.

18 Comments

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).
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 "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.
@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.
@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."
|
7

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).

Comments

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.

2 Comments

This is a really clean and simple solution.
I think the best approach is to keep it simple and just abstract macros with more macros 😂😂😂
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__)) )

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.