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

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

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


person Mathuin    schedule 25.06.2013    source источник
comment
Для справки: 6.5.1.1 Общий выбор. В соответствии с этим первым аргументом _Generic является assignment-expression, тип которого определяет, какой элемент списка выражений (например, cpowl, cpow и т. д.) выбран. Таким образом, невозможно использовать одно выделение _Generic для выбора на основе двух типов/двух выражений.   -  person dyp    schedule 25.06.2013
comment
@DyP это не значит, что нет обходных путей.   -  person Elazar    schedule 25.06.2013
comment
@Elazar Действительно, именно поэтому это не ответ;) Но дело в том, что невозможно использовать одиночный общий выбор.   -  person dyp    schedule 25.06.2013
comment
Я знаю это, я ищу умный макрос, который позволил бы мне лучше использовать выбор типа. Я отредактирую свой пост, он недостаточно ясен.   -  person Mathuin    schedule 25.06.2013
comment
Как насчет использования _generics с функциями с несколькими параметрами, но вы хотите включить только первую? У меня чертовски много времени, чтобы понять синтаксис для этого, вы используете #define Power2Mask(BitOrder,Bits2Mask) _Generic((BitOrder) ... ) (Bits2Mask) или #define Power2Mask(BitOrder,Bits2Mask) _Generic((BitOrder,Bits2Mask) ... ) (BitOrder,Bits2Mask)? Clang вообще не помогает отлаживать это   -  person MarcusJ    schedule 29.07.2017


Ответы (5)


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

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

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

person torek    schedule 25.06.2013
comment
Мало того, что это будет работать только для определенных случаев, это будет очень тонко и подвержено ошибкам - вам нужно будет знать правила преобразования в C наизусть. - person Elazar; 25.06.2013
comment
@Elazar: конечно, но если вы пишете дженерики, вам лучше знать это или иметь шпаргалку со всеми возможными комбинациями типов, пока вы пишете. :-) - person torek; 25.06.2013
comment
Я нашел этот ответ в другом вопросе о SO, но он не соответствовал моим потребностям, поскольку это опасно, как указывает Элазар. +1 однако. - person Mathuin; 25.06.2013
comment
Не опасно и не тонко - прямо и просто. - person chux - Reinstate Monica; 12.03.2020

Поскольку в 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

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

person Elazar    schedule 25.06.2013
comment
Я пытаюсь понять ваш код, но мне он кажется более приятным, даже если он неясен (часть (*((types_unit*)0))). Я немного подожду, прежде чем пометить вопрос как решенный, мне нравится иметь несколько точек зрения. - person Mathuin; 25.06.2013
comment
это выражение типа types_unit. Это то, что нужно макросам typeofX. - person Elazar; 25.06.2013
comment
Нельзя ли это сделать с помощью перечисления? md5 указал мне на это в IRC - person Mathuin; 25.06.2013
comment
Я не вижу, как перечисление может помочь здесь. - person Elazar; 25.06.2013
comment
Это нечто. Лед немного тонок с трюком *(T *)0, но я никогда не видел системы, в которой он не работает, и обычно он используется для реализации offsetof. @Mathuin: типы enum можно использовать на нижнем уровне, но это только усугубляет ситуацию, поскольку тогда вам нужно указать некоторые уникальные, но неиспользуемые константы перечисления для каждого из них. Лучше придерживаться struct (или union). - person torek; 26.06.2013
comment
@torek Я не думаю, что это UB, так как я на самом деле не разыменовываю его - я использую его только как переключатель. Если это проблема, подойдет любое число, отличное от 0. Я ищу способ обобщить это, но это кажется нетривиальным. - person Elazar; 26.06.2013
comment
Замечание о тонком льду восходит к оригинальному стандарту C 1989 года. Кто-то (я понятия не имею, кто) указал, что offsetof можно выполнить с помощью (char *)&((struct foo *)0)->member - (char *)0, который также не следует нулевым указателям, но члены комитета нервничали, заявляя, что это нормально. Я не знаю почему, тривиально сказать, что разработчики компиляторов должны сделать эту работу, и им/нам несложно заставить ее работать (константы времени компиляции уже должны быть обнаружены, ну, во время компиляции...). - person torek; 26.06.2013
comment
@torek Кто-то (я понятия не имею, кто) -- например, я знал. Я помню, как Билл Плаугер задавался вопросом, имеет ли offsetof какую-либо полезность, и я указал, что есть обычное использование (структуры, заканчивающиеся массивами с изменяемым размером), и что существующий код использовал это определение для его реализации. Но я не мог быть первым ... формальный запрос на функцию, безусловно, давал такое определение. - person Jim Balter; 28.06.2013
comment
@JimBalter: мы также использовали моральный эквивалент offsetof (и я изменил его на фактический offsetofs) в ядре, определяя символы для ассемблерного кода. - person torek; 28.06.2013
comment
@torek Под ядром я предполагаю, что вы имели в виду BSD, но у вас это может быть любой из них. :-) - person Jim Balter; 28.06.2013
comment
@JimBalter Поправьте меня, если я ошибаюсь, но не offsetof требует, чтобы значение было равно нулю, чтобы быть переносимым? Здесь это не так; 0 - это просто допустимое выражение. - person Elazar; 28.06.2013
comment
@Elazar offsetof предоставляется реализацией. поэтому переносимость не имеет значения. Реализация offsetof не указана стандартом; это просто должно работать. - person Jim Balter; 28.06.2013
comment
@JimBalter Я упомянул (char *)&((struct foo *)0)->member - (char *)0 - person Elazar; 28.06.2013
comment
@Элазар, я знаю, о чем ты говорил. Вы, кажется, не поняли, что я написал, но я не буду пытаться снова. - person Jim Balter; 28.06.2013
comment
Ну, я надеюсь, что @torek будет достаточно любезен, чтобы помочь мне понять это. Нет ли проблем с переносимостью в реализациях стандартной библиотеки? или я что-то еще упускаю? Я думал, что реализация макроса таким образом, который будет работать для любой цели, является хорошей идеей. - person Elazar; 28.06.2013
comment
@Elazar: я не уверен, о чем ты говоришь. Если вы разработчик, пишущий библиотеку, вы можете написать непереносимый код для каждой цели, переносимый код или сочетание того и другого. Я всегда просто делаю то, что кажется лучшим в данный момент, обычно полностью переносимым, если это легко, но если это слишком сложно, достаточно хорошо для моих целей, и если это слишком сложно, конкретно- к-этой-цели. - person torek; 28.06.2013
comment
Если подумать, ноль здесь вообще не нужен (в отличие от offsetof()). Нужен юридический адрес. - person Elazar; 05.12.2013
comment
Или вы можете написать синтаксический анализатор, который генерирует этот код из более удобного для человека синтаксиса. - person étale-cohomology; 07.07.2021

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

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

Для этого требуется потрясающая библиотека препроцессора Order от Vesa K.

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

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

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

person Leushenko    schedule 07.09.2014

Ну что ж... вот начало решения для макросов с использованием библиотеки препроцессора boost (совместимой с препроцессором C99).

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


Этот пример из ОП

#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__)) )
person dyp    schedule 26.06.2013

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

person Community    schedule 12.07.2017
comment
Это действительно чистое и простое решение. - person jdk1.0; 22.05.2021