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