3

I have a template like this:

template.h
----------
// Declare a function "func_type()"
void JOIN(func_, T)(T t) { return; }
#undef T

which I use like this in order to generate the same function for different types:

example.c
---------
#define T int
#include "template.h"

#define T float
#include "template.h"

I would like to have a single func that I can use instead of funct_int, func_float, etc. My problem with _Generic is that it doesn't seem possible to define the association-list dynamically. In practical terms I'd like to have something like this:

#define func(TYPE) _Generic((TYPE), AUTO_GENERATED_LIST)

instead of manually defining every new type like this:

#define func(TYPE) _Generic((TYPE), int: func_int..., float: func_float...)

Here's an example of code that is not working: https://ideone.com/HN7sst

grep9090
  • 43
  • 5
  • The generic selection is happening in compile time. You want your `AUTO_GENERATED_LIST` to be generated in compile time too? – Eugene Sh. Feb 28 '20 at 14:25
  • > You want your AUTO_GENERATED_LIST to be generated in compile time too? Yes – grep9090 Feb 28 '20 at 14:28
  • Well, you can't have function overloading in C. `void func(int x)` can't co-exist with `void func(float x)` in the same translation unit. Is the problem that you don't want to type out individually named functions that do the same thing, or what? – Lundin Feb 28 '20 at 14:36
  • Not sure what is not working, but a macro is successfully expanding into the list: https://ideone.com/Jh7b8o – Eugene Sh. Feb 28 '20 at 14:37
  • @Lundin `_Generic` exists for this reason, for overloading a function. – grep9090 Feb 28 '20 at 14:38
  • @EugeneSh. In your example you are defining the list manually. Instead I would like something like this https://ideone.com/HN7sst such that I can create the list dynamically every time a new `func_type` is defined from the template file. – grep9090 Feb 28 '20 at 14:42
  • You better write the code you would *like* to work which is not working, so we understand your usecase. Ah, actually you did. Place it into the question body – Eugene Sh. Feb 28 '20 at 14:43
  • So you want each include file to take some macro, and redefine it to contain the previous content + extra. Am I correct? Let's decouple it from `_Generic`. – Eugene Sh. Feb 28 '20 at 14:49
  • If that's the question, then it is pretty much a duplicate of [this](https://stackoverflow.com/questions/4550075/can-i-append-to-a-preprocessor-macro). It has a gcc-specific solution. – Eugene Sh. Feb 28 '20 at 14:54
  • @EugeneSh. Yes. – grep9090 Feb 28 '20 at 14:55
  • @EugeneSh. `push_macro` and `pop_macro` do not seem to work if I push/pop something like this `T: JOIN(func_, T)` because only the final T is expanded which means I end up with this: `_Generic((TYPE), float: func_float..., float: func_float...)`. – grep9090 Feb 28 '20 at 14:58
  • @grep9090 Check out the answer I posted, is this what you wanted to achieve? – Lundin Feb 28 '20 at 15:04
  • Ah.. that `_Pragma` solution will work for the first call of `func` only... – Eugene Sh. Feb 28 '20 at 15:06

1 Answers1

3

I think what you want to do can be achieved with the dreaded "X macros". Create a list such as

#define SUPPORTED_TYPES(X) \
  X(int,   "%d")           \
  X(float, "%f")           \

where int is the type and in this case I used printf format specifier as another item. These can be anything that counts as valid pre-processor tokens.

Then you can generate all functions through an evil macro like this:

#define DEFINE_F(type, fmt) \
void f_##type (type param)   \
{ printf(fmt "\n", param); }

SUPPORTED_TYPES(DEFINE_F)

This creates functions such as void f_int (int param) { printf("%d\n", param); }. That is, very similar to C++ templates - functions doing the same thing but with different types.

You can then write your _Generic macro like this:

void dummy (void* param){}
#define GENERIC_LIST(type, fmt) type: f_##type,
#define func(x) _Generic((x), SUPPORTED_TYPES(GENERIC_LIST) default: dummy)(x)

Here you define the generic asoc. list with GENERIC_LIST, using the type item but ignoring everything else. So it expands to for example int: f_int,.

A problem with this is the old "trailing comma" problem, we can't write _Generic like _Generic((x), int: f_int,)(x) the comma after f_int would mess up the syntax. I solved this with a default clause calling a dummy function, not ideal... might want to stick an assert inside that function.

Full example:

#include <stdio.h>

#define SUPPORTED_TYPES(X) \
  X(int,   "%d")           \
  X(float, "%f")           \


#define DEFINE_F(type, fmt)  \
void f_##type (type param)   \
{ printf(fmt "\n", param); }

SUPPORTED_TYPES(DEFINE_F)


void dummy (void* param){}
#define GENERIC_LIST(type, fmt) type: f_##type,
#define func(x) _Generic((x), SUPPORTED_TYPES(GENERIC_LIST) default: dummy)(x)

int main (void)
{
  int a = 1;
  float b = 2.0f;
  func(a);
  func(b);
}

Output:

1
2.000000

This is 100% ISO C, no extensions.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Thanks for this solution. I've took some time to look into this. It's an interesting solution that I didn't think of, but unfortunately it does not seem to be the answer to my question. The reason is that it still requires to specify all the types in `SUPPORTED_TYPES` manually, whereas I'd like to **append** every new type automatically as they are created (a new type is "created" every time the template is included). – grep9090 Feb 29 '20 at 08:32
  • 1
    @grep9090 What is the actual problem you are trying to solve? Template metaprogramming is a bad idea in C++... so imagine how bad an idea it is in C. – Lundin Mar 02 '20 at 07:25
  • The problem I'm trying to solve is this: **1)** define a generic function, say `binary_search_T()` **2)** the template can be included many times in order to end up with `binary_search_int()`, `binary_search_float()`, etc. **3)** have a single function `binary_search()` that automatically selects the correct `binary_search_T()` based on the input type. The last point, **3)**, is what I'm not able to do because I cannot create the association-list dynamically every time a new `binary_search_T()` is defined. – grep9090 Mar 02 '20 at 15:03
  • @grep9090 Then what's the reason why you can't use standard `bsearch`? It solves this in the "old school" way with function pointer callbacks. Because the search algo itself doesn't care about types. – Lundin Mar 02 '20 at 15:08
  • I have many reasons, but the biggest one is that I want to apply this to other functions and `binary_search()` is just an example. – grep9090 Mar 03 '20 at 08:20
  • @grep9090 You can implement the same sort of API as `bsearch` though. Void pointers and callbacks is quite old-fashioned, but it's a well-known design pattern that might solve the problem. – Lundin Mar 03 '20 at 08:36
  • I was using void and functions pointers before getting involved with _Generic, but in certain circumstances such as recursive functions they introduce a significant performance loss. For example C qsort() is notoriously slower than C++ sort() by a large margin. All my problems would be solved if the C preprocessor allowed to append to a macro, like this `#define FOO FOO, BAR` :-) I think I will use `func_int`, `func_float`, ... – grep9090 Mar 04 '20 at 08:20
  • @grep9090 I guess that's true, `qsort` will have a performance loss since function pointer callbacks can't be inlined, wheras a C++ compiler might "see" the whole implementation of a "functor" (overloaded operator) and inline the calls. I now recall a situation where I was bashing some other C veteran for using generic programming with void pointers instead of using type safe _Generic. They responded by asking me how to implement something like `bsearch` type safe, with _Generic and I couldn't come up with an answer then. Might be worth some further thought :) – Lundin Mar 04 '20 at 10:38
  • @grep9090 Though in practice, if this is truly a performance bottleneck, you are likely better off manually writing the search/sort algo manually for the specific case, and then optimize it for the specific system, in terms of alignment and cache access etc etc. Library functions aren't always the best possible solution (but most of the time they are). – Lundin Mar 04 '20 at 10:39