2

So I started writing a template in C using X-Macros and had the desire to use _Generic() to overload them. Problem is that would require extending a macro. I know I can't have a self-referential macro. And I believe I can't expand the definition of a macro like I would wish. I know I could use other techniques and be done with the code (like flipping my node inside-out and use container_of) but I'll eventually have the same problem when implementing typeof() so I figured it would be best to solve it now if possible.

A toy example (of what I wish would work) is:

#include <stdio.h>

void a(int x){printf("a\n");}
void b(char x){printf("b\n");}

#define mat(x) _Generic((x), int: a(x))
#define temp(x) mat(x)
#define mat(x) _Generic((x), char: b(x), default: temp(x))

int main(void)
    {
    int x;
    mat(x);
    }

Is there any way to accomplish this redefinition or something equivalent? If not is there a way to do it in a POSIX compliant way (M4, sh, other)?

For answers: This code pattern seems generally useful in C11 so only using C if possible. I really hate most preprocessors so if its not the CPP then it's definitely my last choice.

Black
  • 185
  • 9
  • 1
    You could use a shellscript to generate the desired macros / inline functions. – fuz Oct 03 '15 at 15:08
  • @FUZxxl I was thinking that was probably what I might end up having to do. But seeing as this is actually broadly useful I was hoping someone might know something I don't (or be more creative). The shellscript route makes me wonder if it wouldn't be better to write a simple preprocessor that does only this then calls the compiler. Or call the CPP twice... Are there any cases where that would break code (that people actually use)? – Black Oct 03 '15 at 15:23

2 Answers2

4

It is provably impossible to change a macro in any way without rewriting it from scratch. In the standard 6.10.3, you have:

An identifier currently defined as an object-like macro shall not be redefined by another #define preprocessing directive unless the second definition is an object-like macro definition and the two replacement lists are identical. Likewise, an identifier currently defined as a function-like macro shall not be redefined by another #define preprocessing directive unless the second definition is a function-like macro definition that has the same number and spelling of parameters, and the two replacement lists are identical.

So, you either have that a macro will expand exactly according to its definition, or else you need to #undef it. If you #undef it, to redefine it you need to write it from scratch. The old definition will not be available at that point, even to help in defining the new version.

You can't "reach" the old definition through any sort of intermediary because the preprocessor acts on definitions active at the point of the invocation, not at the points where the macros involved were defined. So, if you define temp(x) like you did, it expands in the context of your new definition of mat, not the old one (not to mention that there needed to be an undef between the two definitions of mat(x) and the anti-recursion rules will stop macro expansion at mat(x) anyway).

In short, there is absolutely no way, according to the standard, to redefine a macro in a way that is based on its original definition. This is mathematically provable from the standard.

Is there any reason you don't just modify the original definitions of the macros to deal with each type you are interested in? Or use some modification of the naming scheme to indicate your own version (like append _o to indicate a macro that deals with more types but ultimately relies on the corresponding macro)? Or, better yet, modify the names of the original macros, so that you can redefine them but have the originals available?

Edit

Going by the comment below, here is one way to maybe achieve some of what OP desires. Its not perfect, but the C preprocessor does have limitations. Here is an example that converts objects into a modifiable string.

write this in the header where you define the macro in the first place:

#define get_length(x) get_length_help(x)
#define get_length_help(x) sizeof(#x)

#define to_string(x) _Generic( (x),                                        \
   int: sprintf( (char[get_length(INT_MAX)]){0}, "%d", x ),                \
   char: sprintf( (char[2]){0}, "%c", x ),                                 \
   to_string_1,
   to_string_2,
   // ...
   )

// make sure all extra spots expand to nothing until used
#define to_string_1
#define to_string_2
// ...

// macro to test if an extension slot is used
// requires that extensions do not begin with '('
#define used(...) used_help2( (used_help1 __VA_ARGS__ ()), 0, 1, )
#define used_help1() ),(
#define used_help2(_1,_2,_3,...) _3

Write this in foo.c

typedef struct {
   int x,
   int y
   } plot_point

// this part is important, all generic selections must be valid expressions
// for the other types as well
#define safe_plot_point(x) _Generic( x, plot_point: x, default: (plot_point){0,0} )

// this could define a comma delimited list of selection statements as well
#define to_string_foo                                                      \
   plot_point: sprintf(                                                    \
      (char[get_length((INT_MAX,INT_MAX))]){0},                            \
      "(%i,%i)",                                                           \
      safe_plot_point(x).x, safe_plot_point(x).y                           \
      )

// this is the only real complication for the user
#if used(to_string_1)
#undef to_string_1
#define to_string_1 to_string_foo
#elif used(to_string_2)
#undef to_string_2
#define to_string_2 to_string_foo
// ...
#else
_Static_assert( 0, "no to_string_#'s left!" );
#endif

So, you can get some degree of modifiability. Note that no macro could just perform the modification, as it is undefined behavior for a macro to expand into another preprocessing directives, and as stated above preprocessing directives are the only way to change the behavior of a macro. Macro invocations are simply blind to other invocations, they only see other definitions.

Also note that you could define any number of extension slots. You can define more slots than a given file checks for, and only add the extra lines (the #if's), if they are needed.

Kyle
  • 878
  • 6
  • 14
  • I was trying to create a black box of source code. To extend the features you would just use a macro `extend()` and it would handle all the dirty work of adding your type to the code. No need to understand how it all worked. A "Compile-time API". I'd came to the same conclusion you did and decided having a "plugin architecture" would achieve the same objective post-compile. That comes with many other issues though. I could of course rely on the user to understand the code to create a new extended macro but there's a part of the code that hurts the author's brain and it's only ~9 lines. – Black Mar 26 '16 at 03:02
  • Similar to `typeof()` you would need to extend it all the time. In my case though you would need to understand `struct node` which would require understanding the aforementioned 9 lines. – Black Mar 26 '16 at 03:08
  • I added a description of one approach to achieve some of what you want. The preprocessor is fairly limited, so you can't really do much better than the above without totally changing the approach. – Kyle Mar 26 '16 at 07:30
  • The addition doesn't work well. You wouldn't start implementing `typeof()` by limiting the number of slots you had. And if you're going back and adding slots you may as well be writing what you want to add straight into `typeof()`. Thanks for the help though. – Black Mar 28 '16 at 05:12
2

This is an old but interesting question that really illuminates how limited _Generic is. Building on Kyle's answer, I think we can achieve user extendibility with a much cleaner interface without any (visible) slots or #undefs if we can just convert the extension code from macro text to regular code. For this we need a few macro tricks and a helping header.

In the below example, we want “foo” to be a user-extendible function-like macro that calls a different function depending on the argument type (for the sake of demonstration, that function takes no arguments and just return’s the type’s name as a string).

foo.h:

#ifndef FOO_H
#define FOO_H
#include <stdio.h>

// The end result of the below macros is that if_foo_type_def( FOO_TYPE_N )( some_code )
// will expand to some_code if FOO_TYPE_N is defined (as an empty macro) and nothing if it
// is not defined
#define concat( a, b ) a ## b
#define if_foo_type_def_( ... ) __VA_ARGS__
#define if_foo_type_def_FOO_TYPE_1( ... )
#define if_foo_type_def_FOO_TYPE_2( ... )
#define if_foo_type_def_FOO_TYPE_3( ... )
#define if_foo_type_def_FOO_TYPE_4( ... )
#define if_foo_type_def_FOO_TYPE_5( ... )
#define if_foo_type_def_FOO_TYPE_6( ... )
#define if_foo_type_def_FOO_TYPE_7( ... )
#define if_foo_type_def_FOO_TYPE_8( ... )
#define if_foo_type_def_FOO_TYPE_9( ... )
#define if_foo_type_def_FOO_TYPE_10( ... )
#define if_foo_type_def_FOO_TYPE_11( ... )
#define if_foo_type_def_FOO_TYPE_12( ... )
#define if_foo_type_def_FOO_TYPE_13( ... )
#define if_foo_type_def_FOO_TYPE_14( ... )
#define if_foo_type_def_FOO_TYPE_15( ... )
#define if_foo_type_def_FOO_TYPE_16( ... )
#define if_foo_type_def_FOO_TYPE_17( ... )
#define if_foo_type_def_FOO_TYPE_18( ... )
#define if_foo_type_def_FOO_TYPE_19( ... )
#define if_foo_type_def_FOO_TYPE_20( ... )
#define if_foo_type_def_FOO_TYPE_21( ... )
#define if_foo_type_def_FOO_TYPE_22( ... )
#define if_foo_type_def_FOO_TYPE_23( ... )
#define if_foo_type_def_FOO_TYPE_24( ... )
#define if_foo_type_def_FOO_TYPE_25( ... )
#define if_foo_type_def_FOO_TYPE_26( ... )
#define if_foo_type_def_FOO_TYPE_27( ... )
#define if_foo_type_def_FOO_TYPE_28( ... )
#define if_foo_type_def_FOO_TYPE_29( ... )
#define if_foo_type_def_FOO_TYPE_30( ... )
#define if_foo_type_def_FOO_TYPE_31( ... )
#define if_foo_type_def_FOO_TYPE_32( ... )
#define if_foo_type_def( type ) concat( if_foo_type_def_, type )

// Extendable macro
#define foo( a )                                                                \
    _Generic( (a),                                                              \
        if_foo_type_def( FOO_TYPE_1 )( foo_type_1_type: foo_type_1_func, )      \
        if_foo_type_def( FOO_TYPE_2 )( foo_type_2_type: foo_type_2_func, )      \
        if_foo_type_def( FOO_TYPE_3 )( foo_type_3_type: foo_type_3_func, )      \
        if_foo_type_def( FOO_TYPE_4 )( foo_type_4_type: foo_type_4_func, )      \
        if_foo_type_def( FOO_TYPE_5 )( foo_type_5_type: foo_type_5_func, )      \
        if_foo_type_def( FOO_TYPE_6 )( foo_type_6_type: foo_type_6_func, )      \
        if_foo_type_def( FOO_TYPE_7 )( foo_type_7_type: foo_type_7_func, )      \
        if_foo_type_def( FOO_TYPE_8 )( foo_type_8_type: foo_type_8_func, )      \
        if_foo_type_def( FOO_TYPE_9 )( foo_type_9_type: foo_type_9_func, )      \
        if_foo_type_def( FOO_TYPE_10 )( foo_type_10_type: foo_type_10_func, )   \
        if_foo_type_def( FOO_TYPE_11 )( foo_type_11_type: foo_type_11_func, )   \
        if_foo_type_def( FOO_TYPE_12 )( foo_type_12_type: foo_type_12_func, )   \
        if_foo_type_def( FOO_TYPE_13 )( foo_type_13_type: foo_type_13_func, )   \
        if_foo_type_def( FOO_TYPE_14 )( foo_type_14_type: foo_type_14_func, )   \
        if_foo_type_def( FOO_TYPE_15 )( foo_type_15_type: foo_type_15_func, )   \
        if_foo_type_def( FOO_TYPE_16 )( foo_type_16_type: foo_type_16_func, )   \
        if_foo_type_def( FOO_TYPE_17 )( foo_type_17_type: foo_type_17_func, )   \
        if_foo_type_def( FOO_TYPE_18 )( foo_type_18_type: foo_type_18_func, )   \
        if_foo_type_def( FOO_TYPE_19 )( foo_type_19_type: foo_type_19_func, )   \
        if_foo_type_def( FOO_TYPE_20 )( foo_type_20_type: foo_type_20_func, )   \
        if_foo_type_def( FOO_TYPE_21 )( foo_type_21_type: foo_type_21_func, )   \
        if_foo_type_def( FOO_TYPE_22 )( foo_type_22_type: foo_type_22_func, )   \
        if_foo_type_def( FOO_TYPE_23 )( foo_type_23_type: foo_type_23_func, )   \
        if_foo_type_def( FOO_TYPE_24 )( foo_type_24_type: foo_type_24_func, )   \
        if_foo_type_def( FOO_TYPE_25 )( foo_type_25_type: foo_type_25_func, )   \
        if_foo_type_def( FOO_TYPE_26 )( foo_type_26_type: foo_type_26_func, )   \
        if_foo_type_def( FOO_TYPE_27 )( foo_type_27_type: foo_type_27_func, )   \
        if_foo_type_def( FOO_TYPE_28 )( foo_type_28_type: foo_type_28_func, )   \
        if_foo_type_def( FOO_TYPE_29 )( foo_type_29_type: foo_type_29_func, )   \
        if_foo_type_def( FOO_TYPE_30 )( foo_type_30_type: foo_type_30_func, )   \
        if_foo_type_def( FOO_TYPE_31 )( foo_type_31_type: foo_type_31_func, )   \
        if_foo_type_def( FOO_TYPE_32 )( foo_type_32_type: foo_type_32_func, )   \
        /* Will cause a compilation error: */                                   \
        default: "ERROR CAUSE: UNDEFINED FOO TYPE"                              \
    )( a )                                                                      \

// Types supported intrinsically
// We could hard code these into the above macro, but I think it's actually less
// complicated and confusing to define them using the same interface provided to
// the user.

#define NEW_FOO_TYPE_TYPE char
#define NEW_FOO_TYPE_FUNC return "char";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE unsigned char
#define NEW_FOO_TYPE_FUNC return "unsigned char";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE signed char
#define NEW_FOO_TYPE_FUNC return "signed char";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE int
#define NEW_FOO_TYPE_FUNC return "int";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE unsigned int
#define NEW_FOO_TYPE_FUNC return "unsigned int";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE long
#define NEW_FOO_TYPE_FUNC return "long";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE unsigned long
#define NEW_FOO_TYPE_FUNC return "unsigned long";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE long long
#define NEW_FOO_TYPE_FUNC return "long long";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE unsigned long long
#define NEW_FOO_TYPE_FUNC return "unsigned long long";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE float
#define NEW_FOO_TYPE_FUNC return "float";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE double
#define NEW_FOO_TYPE_FUNC return "double";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE long double
#define NEW_FOO_TYPE_FUNC return "long double";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE _Bool
#define NEW_FOO_TYPE_FUNC return "bool";
#include "new_foo_type.h"

#endif

new_foo_type.h:

// Check header is being user properly first
#if !defined( NEW_FOO_TYPE_TYPE ) || !defined( NEW_FOO_TYPE_FUNC )
#error You need to define NEW_FOO_TYPE_TYPE and NEW_FOO_TYPE_FUNC before including a new foo type
#endif

// Convert user-supplied-via-macro type and code into regular code and place it
// in the first empty slot

#define define_foo_type_type_and_func( num )            \
    typedef NEW_FOO_TYPE_TYPE foo_type_##num##_type;    \
                                                        \
    static inline char *foo_type_##num##_func()         \
    {                                                   \
        NEW_FOO_TYPE_FUNC                               \
    }                                                   \

#ifndef FOO_TYPE_1
define_foo_type_type_and_func( 1 )
#define FOO_TYPE_1

#elif !defined( FOO_TYPE_2 )
define_foo_type_type_and_func( 2 )
#define FOO_TYPE_2

#elif !defined( FOO_TYPE_3 )
define_foo_type_type_and_func( 3 )
#define FOO_TYPE_3

#elif !defined( FOO_TYPE_4 )
define_foo_type_type_and_func( 4 )
#define FOO_TYPE_4

#elif !defined( FOO_TYPE_5 )
define_foo_type_type_and_func( 5 )
#define FOO_TYPE_5

#elif !defined( FOO_TYPE_6 )
define_foo_type_type_and_func( 6 )
#define FOO_TYPE_6

#elif !defined( FOO_TYPE_7 )
define_foo_type_type_and_func( 7 )
#define FOO_TYPE_7

#elif !defined( FOO_TYPE_8 )
define_foo_type_type_and_func( 8 )
#define FOO_TYPE_8

#elif !defined( FOO_TYPE_9 )
define_foo_type_type_and_func( 9 )
#define FOO_TYPE_9

#elif !defined( FOO_TYPE_10 )
define_foo_type_type_and_func( 10 )
#define FOO_TYPE_10

#elif !defined( FOO_TYPE_11 )
define_foo_type_type_and_func( 11 )
#define FOO_TYPE_11

#elif !defined( FOO_TYPE_12 )
define_foo_type_type_and_func( 12 )
#define FOO_TYPE_12

#elif !defined( FOO_TYPE_13 )
define_foo_type_type_and_func( 13 )
#define FOO_TYPE_13

#elif !defined( FOO_TYPE_14 )
define_foo_type_type_and_func( 14 )
#define FOO_TYPE_14

#elif !defined( FOO_TYPE_15 )
define_foo_type_type_and_func( 15 )
#define FOO_TYPE_15

#elif !defined( FOO_TYPE_16 )
define_foo_type_type_and_func( 16 )
#define FOO_TYPE_16

#elif !defined( FOO_TYPE_17 )
define_foo_type_type_and_func( 17 )
#define FOO_TYPE_17

#elif !defined( FOO_TYPE_18 )
define_foo_type_type_and_func( 18 )
#define FOO_TYPE_18

#elif !defined( FOO_TYPE_19 )
define_foo_type_type_and_func( 19 )
#define FOO_TYPE_19

#elif !defined( FOO_TYPE_20 )
define_foo_type_type_and_func( 20 )
#define FOO_TYPE_20

#elif !defined( FOO_TYPE_21 )
define_foo_type_type_and_func( 21 )
#define FOO_TYPE_21

#elif !defined( FOO_TYPE_22 )
define_foo_type_type_and_func( 22 )
#define FOO_TYPE_22

#elif !defined( FOO_TYPE_23 )
define_foo_type_type_and_func( 23 )
#define FOO_TYPE_23

#elif !defined( FOO_TYPE_24 )
define_foo_type_type_and_func( 24 )
#define FOO_TYPE_24

#elif !defined( FOO_TYPE_25 )
define_foo_type_type_and_func( 25 )
#define FOO_TYPE_25

#elif !defined( FOO_TYPE_26 )
define_foo_type_type_and_func( 26 )
#define FOO_TYPE_26

#elif !defined( FOO_TYPE_27 )
define_foo_type_type_and_func( 27 )
#define FOO_TYPE_27

#elif !defined( FOO_TYPE_28 )
define_foo_type_type_and_func( 28 )
#define FOO_TYPE_28

#elif !defined( FOO_TYPE_29 )
define_foo_type_type_and_func( 29 )
#define FOO_TYPE_29

#elif !defined( FOO_TYPE_30 )
define_foo_type_type_and_func( 30 )
#define FOO_TYPE_30

#elif !defined( FOO_TYPE_31 )
define_foo_type_type_and_func( 31 )
#define FOO_TYPE_31

#elif !defined( FOO_TYPE_32 )
define_foo_type_type_and_func( 32 )
#define FOO_TYPE_32

#else
#error Sorry, too many foo types!
#endif

// Undef automatically so that the user doesn't have to do it
#undef NEW_FOO_TYPE_TYPE
#undef NEW_FOO_TYPE_FUNC

main.c (the user's file):

#include "foo.h" // foo macro

// Define two new types

typedef struct
{
    char data;
} my_type;

typedef struct
{
    char data;
} my_other_type;

// Make foo support those types (this is the user's interface)

#define NEW_FOO_TYPE_TYPE my_type
#define NEW_FOO_TYPE_FUNC return "my_type";
#include "new_foo_type.h"

#define NEW_FOO_TYPE_TYPE my_other_type
#define NEW_FOO_TYPE_FUNC return "my_other_type";
#include "new_foo_type.h"

// Test

int main()
{
    int a;
    my_type b;
    my_other_type c;
    int *d;
    
    printf( "%s\n", foo( a ) );     // Prints "int"
    printf( "%s\n", foo( b ) );     // Prints "my_type"
    printf( "%s\n", foo( c ) );     // Prints "my_other_type"
    // printf( "%s\n", foo( d ) );  // Causes compiler error because int* has not been added
                                    // and is not supported intrinsically

    return 0;
}

So to add support for a new type, the user only needs to define NEW_FOO_TYPE_TYPE and NEW_FOO_TYPE_FUNC and then include new_foo_type.h. While not very C-ish, I think this interface is rather simple and intuitive.

In my example, NEW_FOO_TYPE_FUNC must be a function body, but it could just as easily be a function name if you just call that function from within the new function defined in new_foo_type.h (declare a pointer to the user-supplied function first to generate a warning or error if its signature is incorrect).

The drawback of this approach is that behind the scenes, it’s still “slotted”, so you have guess how many slots you should reasonably support (just pick a large number). Also, many people would consider it macro hell.

A really nice thing about it is that it can be used with any kind of macro that needs extendibility, not just _Generic based ones.

Any feedback or improvements are welcome :)

Jackson Allan
  • 727
  • 3
  • 11