I understand C11 generics for one-parameter functions, like this: (from here)
#define acos(X) _Generic((X), \\
long double complex: cacosl, \\
double
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.
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.
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
.)
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__)) )
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:
OVERLOADED_ARG_TYPES
-O1
)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).