Общие для многопараметрических функций C в C11

Я понимаю C11 generics для однопараметрических функций, например: (от здесь)

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

Но, кажется, боль для функций с двумя аргументами, вам нужно вложить вызовы на _Generic, что действительно уродливо; Выдержка из того же блога:

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

Есть ли способ иметь больше читаемых человеческих обобщений для многопараметрических функций, например, это:

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

Заранее благодарим за ваши ответы. Основная идея заключалась бы в создании макрокоманды для _Generic.

Ответ 1

Учитывая, что управляющее выражение _Generic не оценивается, я предложил применить некоторую арифметическую операцию, которая выполняет соответствующее комбинирование типов, и включение результата. Таким образом:

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

Конечно, это работает только в определенных случаях, но вы всегда можете расширять те, для которых "смятый" тип не помогает. (Это позволило бы позаботиться о array-N-of- char vs char *, например, как и в связанном примере printnl, а затем, если объединенный тип int, можно вернуться назад и проверьте char и short.)

Ответ 2

Так как C не имеет кортежей, давайте сделаем наши собственные кортежи:

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 )

Это код клиента:

#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;
}

И он работает:

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

Да, вам все равно придется писать код в экспоненциальном размере. Но, по крайней мере, вы сможете его повторно использовать.

Ответ 3

О, хорошо... здесь начало макро-решения с использованием библиотеки препроцессора повышения (C99-препроцессор-совместимый).

Идея состояла в том, чтобы предоставить общий синтаксис, который позволяет записывать вложенные общие варианты для произвольного количества аргументов. Чтобы это было "просто", выражение для выбора одинаково для всех элементов на одном уровне выбора (вы могли бы определить другой синтаксис, чтобы изменить управляющее выражение для каждого выбора уровня отдельно.).


Этот пример из OP

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

становится

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

Хотя я думаю, что можно немного изменить его, чтобы получить что-то вроде:

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

который может расширяться для трех параметров:

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

Еще один комментарий: я думаю, что синтаксис OP также может быть выполнен, но он не так гибкий, поскольку вам нужно повторить первый аргумент для каждого возможного второго аргумента, например.

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

Пример OP в моем синтаксисе. Обратите внимание, что здесь вы не получаете многого, поскольку вам все равно нужно указывать каждый тип отдельно и в этом случае второй тип несколько раз для разных первых типов.

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

Это разрешено:

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

Что, переформатировано:

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

Из-за рекурсивного характера мне пришлось ввести копии макросов; это решение также нуждается в очистке (я немного устал). Макросы:

#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__)) )

Ответ 4

Здесь приведена версия, в которой вам требуется только написать линейную сумму кода вручную, все из которых напрямую связаны с рассматриваемым вопросом (больших деревьев с определенными вручную типами). Во-первых, пример использования:

#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"
}

Это, вероятно, самый легкий синтаксис, который вы получите для этого.

Теперь для недостатков/ограничений:

  • вам нужно объявить все типы аргументов для перегруженных функций в списке OVERLOADED_ARG_TYPES
  • Типы аргументов должны быть одним словом (неважная проблема, благодаря typedef, но нужно помнить)
  • это приводит к огромному размытию кода на фактическом сайте вызова (хотя это легко раздувать компилятор для оптимизации - GCC будет в -O1)
  • опирается на массивную библиотеку PP (см. ниже)

Вы также должны определить функцию X_default, которая не принимает аргументов; не добавляйте это в блок объявления перегрузки. Это используется для не-совпадений (если вы хотите вызвать его напрямую, вызовите перегрузку с любым несоответствующим значением, например, как составная литеральная анонимная структура или что-то еще).

Здесь 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) \
    ) \
)

Для Vesa K требуется awesome Заказать препроцессорную библиотеку.

Как это работает: объявление OVERLOAD_ARG_TYPES используется для создания перечисления, в котором перечислены все используемые типы аргументов в качестве констант. Каждый вызов перегруженного имени затем может быть заменен в коде вызывающего абонента большой тройной операцией, отправляющей между всеми реализациями (справа номера аргумента). Отправка работает с помощью _Generic для генерации значений перечисления из типов аргументов, поместите их в массив и автоматически создайте функцию диспетчера, чтобы вернуть идентификатор (позицию в исходном блоке) для этой комбинации типов. Если идентификатор совпадает, функция вызывается. Если аргументы имеют неправильный тип, для неиспользуемого вызова реализации генерируются фиктивные значения, чтобы избежать несоответствия типов.

Технически это связано с отправкой "runtime", но поскольку каждый идентификатор типа является постоянным, а функция диспетчера static inline, все это должно быть легко для компилятора для оптимизации, за исключением требуемого вызова (и GCC делает действительно оптимизируйте все это).

Это уточнение техники, ранее опубликованной здесь (та же идея, теперь с симпатичным и сверхлегким синтаксисом).

Ответ 5

Мне действительно кажется, что вышеупомянутые решения не намного проще или чище, чем оригинальная реализация OP. Я считаю, что лучший подход - сохранить его простым и просто абстрактным макросом с большим количеством макросов. Ниже приведен пример.

#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 - макрос для двух общих дженериков. Это можно было бы расширить до _G3 или более легко. Трюк состоит в том, чтобы просто сделать это нормально, а затем построить макрос из его формы.