Combining _Generic macros

≡放荡痞女 提交于 2020-01-03 19:07:07

问题


I am delighted by C11's _Generic mechanism - switching on type is something I miss from C++. It is however proving difficult to compose.

For an example, given functions:

bool write_int(int);
bool write_foo(foo);
bool write_bar(bar);
// bool write_unknown is not implemented

I can then write

#define write(X) _Generic((X), \
  int : write_int, \
  foo: write_foo, \
  bar: write_bar, \
  default: write_unknown)(X)

and, provided I don't try to use &write or pass it to a function, I can call write(obj) and, provided obj is an instance of one of those types, all is well.

However, in general foo and bar are entirely unrelated to each other. They are defined in different headers, rarely (but occasionally) used together in a single source file. Where then should the macro expanding to the _Generic be written?

At present, I am accumulating header files called things like write.h, equal.h, copy.h, move.h each of which contains a set of function prototypes and a single _Generic. This is workable, but not brilliant. I don't like the requirement to collect together a list of every type in the program in a single place.

I would like to be able to define type foo in a header file, along with the function write_foo, and somehow have the client code able to call the 'function' write. Default looks like a vector through which this could be achieved.

The closest match I can find on this site is c11 generic adding types which has a partial solution, but it's not quite enough for me to see how to combine the various macros.

Let's say that, somewhere in a header file that defines write_bar, we have an existing macro definition:

#define write(x) _Generic((x), bar: write_bar, default: some_magic_here)(x)

Or we could omit the trailing (x)

#define write_impl(x) _Generic((x), bar: write_bar, default: some_magic_here)

Further down in this header, I would like a version of write() that handles either foo or bar. I think it needs to call the existing macro in its default case, but I don't believe the preprocessor is able to rename the existing write macro. If it were able to, the following could work:

#ifndef WRITE_3
#define WRITE_3(X) write(x)
#undef write(x)
#define write(x) __Generic((x),foo: write_foo,default: WRITE_3)(x)

Having just typed that out I can sort-of see a path forward:

// In bar.h
#ifndef WRITE_1
#define WRITE_1(x) __Generic((x), bar: write_bar)
#elif !defined(WRITE_2)
#define WRITE_2(x) __Generic((x), bar: write_bar)
#elif !defined(WRITE_3)
#define WRITE_3(x) __Generic((x), bar: write_bar)
#endif
// In foo.h
#ifndef WRITE_1
#define WRITE_1(x) __Generic((x), foo: write_foo)
#elif !defined(WRITE_2)
#define WRITE_2(x) __Generic((x), foo: write_foo)
#elif !defined(WRITE_3)
#define WRITE_3(x) __Generic((x), foo: write_foo)
#endif
// In write.h, which unfortunately needs to be included after the other two
// but happily they can be included in either order
#ifdef WRITE_2
#define write(x) WRITE_1(x) WRITE_2(x) (x)
#elif
// etc
#endif

This doesn't actually work though, since I can't find a way to make WRITE_N(x) expand to nothing when x doesn't match the argument list. I see the error

controlling expression type 'struct foo' not compatible with any generic association type

Or

expected expression // attempting to present an empty default clause

I believe to distribute the write() definition between several files | macros I need to work around either of the above. A _Generic clause which reduces to nothing in the default case would work, as would one which reduces to nothing if none of the types match.

Getting yet more hackish, if the functions take a pointer to a struct instead of an instance of one, and I provide write_void(void*x) {(void)x;} as the default option, then the code does compile and run. However, expanding write as

write(x) => write_void(x); write_foo(x); write_void(x);

is clearly pretty bad in itself, plus I don't really want to pass everything by pointer.

So - can anyone see a way to define a single _Generic 'function' incrementally, i.e. without starting with a list of all types it will map over? Thank you.


回答1:


The need for type-generic functions across multiple, unrelated files suggests that the program design is poor.

Either those files are related and should share a common parent ("abstract base class") where the type-generic macros and function declarations can then be stated.

Or they are unrelated, but share some common method for whatever reason, in which case you need to invent a common, generic abstraction layer interface which they can then implement. You should always consider the program design on a system level the first thing you do.

This answer does not use _Generic, but proposes a different program design entirely.


To take the example from a comment, with bool equal(T lhs, T rhs). That's the latter of the above two cases, a common interface shared by multiple modules. The first thing to observe is that this is a functor, a function which can be used in turn by generic algorithms such as search/sort algorithms. The C standard suggests how functors should preferably be written:

int compare (const void* p1, const void* p2)

This is the format used by standard functions bsearch and qsort. Unless you have good reasons, you shouldn't deviate from that format, because if you don't, you'll get searching & sorting for free. Also, this form has the advantage of doing lesser, greater and equal checks all in the same function.

The classic C way to implement a common interface for such a function in C would be a header containing this macro:

Interface header:

#define compare(type, x, y) (compare_ ## type(x, y))

Module that implements the header:

// int.c
int compare_int (const void* p1, const void* p2)
{
  return *(int*)p1 - *(int*)p2;
}

Caller:

if( compare(int, a, b) == 0 )
{
  // equal
}

This has the advantage of abstraction: the interface header file doesn't need to know all the types used. The disadvantage is that there is no type safety what-so-ever.

(But this is C, you'll never get 100% type safety through the compiler. Use static analysis if it is a big concern.)

With C11 you can improve type safety somewhat by introducing a _Generic macro. There's a big problem with that though: that macro has to know about all existing types in advance, so you can't put it in an abstract interface header. Rather, it should not be in a common header because then you'll create a tight coupling between every single, unrelated module using that header. You could make such a macro in the calling application, not to define an interface, but to ensure type safety.


What you could do instead, is to enforce an interface through inheritance of an abstract base class:

// interface.h
typedef int compare_t (const void* p1, const void* p2);

typedef struct data_t data_t; // incomplete type

typedef struct
{
  compare_t* compare;
  data_t*    data;
} interface_t;

The module that inherits the interface sets the compare function pointer to point at the specific comparison function, upon object creation. data is private to the module and could be anything. Suppose we create a module called "xy" that inherits the above interface:

//xy.c
struct data_t
{
  int x;
  int y;
};

static int compare_xy (const void* p1, const void* p2)
{
  // compare an xy object in some meaningful way
}

void xy_create (interface_t* inter, int x, int y)
{
  inter->data = malloc(sizeof(data_t));
  assert(inter->data != NULL);

  inter->compare = compare_xy;
  inter->data->x = x;
  inter->data->y = y;
}

A caller can then work with the generic interface_t and call the compare member. We've achieved polymorphism, as the type-specific compare function will then get called.




回答2:


Based loosely on Leushenko's answer to multiparameter generics I have come up with the following horrible solution. It requires that the arguments will be passed by pointer, and the boilerplate involved is pretty bad. It does compile and run though, in a fashion which allows functions to return a value.

// foo.h
#ifndef FOO
#define FOO
#include <stdio.h>
#include <stdbool.h>

struct foo
{
  int a;
};

static inline int write_foo(struct foo* f)
{
  (void)f;
  return printf("Writing foo\n");
}

#if !defined(WRITE_1)
#define WRITE_1
#define WRITE_PRED_1(x) _Generic((x), struct foo * : true, default : false)
#define WRITE_CALL_1(x)                         \
  _Generic((x), struct foo *                    \
           : write_foo((struct foo*)x), default \
           : write_foo((struct foo*)0))

#elif !defined(WRITE_2)
#define WRITE_2
#define WRITE_PRED_2(x) _Generic((x), struct foo * : true, default : false)
#define WRITE_CALL_2(x)                         \
  _Generic((x), struct foo *                    \
           : write_foo((struct foo*)x), default \
           : write_foo((struct foo*)0))
#elif !defined(WRITE_3)
#define WRITE_3
#define WRITE_PRED_3(x) _Generic((x), struct foo * : true, default : false)
#define WRITE_CALL_3(x)                         \
  _Generic((x), struct foo *                    \
           : write_foo((struct foo*)x), default \
           : write_foo((struct foo*)0))
#endif

#endif

// bar.h
#ifndef BAR
#define BAR
#include <stdio.h>
#include <stdbool.h>

struct bar
{
  int a;
};

static inline int write_bar(struct bar* b)
{
  (void)b;
  return printf("Writing bar\n");
}

#if !defined(WRITE_1)
#define WRITE_1
#define WRITE_PRED_1(x) _Generic((x), struct bar * : true, default : false)
#define WRITE_CALL_1(x)                         \
  _Generic((x), struct bar *                    \
           : write_bar((struct bar*)x), default \
           : write_bar((struct bar*)0))

#elif !defined(WRITE_2)
#define WRITE_2
#define WRITE_PRED_2(x) _Generic((x), struct bar * : true, default : false)
#define WRITE_CALL_2(x)                         \
  _Generic((x), struct bar *                    \
           : write_bar((struct bar*)x), default \
           : write_bar((struct bar*)0))
#elif !defined(WRITE_3)
#define WRITE_3
#define WRITE_PRED_3(x) _Generic((x), struct bar * : true, default : false)
#define WRITE_CALL_3(x)                         \
  _Generic((x), struct bar *                    \
           : write_bar((struct bar*)x), default \
           : write_bar((struct bar*)0))
#endif

#endif

// write.h
#ifndef WRITE
#define WRITE

#if defined(WRITE_3)
#define write(x)                                                        \
  WRITE_PRED_1(x) ? WRITE_CALL_1(x) : WRITE_PRED_2(x) ? WRITE_CALL_2(x) \
                                                      : WRITE_CALL_3(x)
#elif defined(WRITE_2)
#define write(x) WRITE_PRED_1(x) ? WRITE_CALL_1(x) : WRITE_CALL_2(x)

#elif defined(WRITE_1)
#define write(x) WRITE_CALL_1(x)
#else
#error "Write not defined"
#endif

#endif

// main.c
#include "foo.h"
#include "bar.h"

#include "write.h"

int main()
{
  struct foo f;
  struct bar b;

  int fi = write(&f);
  int bi = write(&b);

  return fi + bi;
}

I really hope there's a better way than this.



来源:https://stackoverflow.com/questions/33905692/combining-generic-macros

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!