Generics for multiparameter C functions in C11

前端 未结 5 1326
独厮守ぢ
独厮守ぢ 2020-12-13 07:13

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

#define acos(X) _Generic((X), \\
    long double complex: cacosl, \\
    double         


        
相关标签:
5条回答
  • 2020-12-13 07:43

    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.

    0 讨论(0)
  • 2020-12-13 07:43

    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.

    0 讨论(0)
  • 2020-12-13 07:50

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

    0 讨论(0)
  • 2020-12-13 07:53

    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__)) )
    
    0 讨论(0)
  • 2020-12-13 07:58

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

    0 讨论(0)
提交回复
热议问题