3

I'm trying to eliminate a bunch of boilerplate code by using macros.

Here's what works. I can replace:

int do_register_script(struct context *L)
{
    method_type const _instance_methods[] = {
        {"new", __new},
        {"delete", __delete}
        {NULL, NULL}
    };
    register_type(L, script, _instance_methods, 0);
    return 1;
}

with a macro

#define do_register_type(name) \
    int do_register_ ## name(struct context *L) \
    { \
        method_type const _instance_methods[] = { \
            {"new", __new}, \
            {"delete", __delete}, \
            {NULL, NULL} \
        }; \
        register_type(L, name, _instance_methods, 0); \
        return 1; \
    }

like so:

do_register_type(script);

which is perfect!

But I also have some that look like this:

int do_register_rectangle(struct context *L)
{
    method_type const _instance_methods[] = {
        {"new", __new},
        {"delete", __delete},
        {"area", area},
        {"perimeter", perimeter}
        {NULL, NULL}
    };
    register_type(L, rectangle, _instance_methods, 0);
    return 1;
}

And now the above macro doesn't work.

How can I add another parameter to the macro to support this?

I'm using C, not C++, so no templates.

UPDATE: Also sometimes the code this is going in uses aliases for the names

        {"area", area},
        {"Area", area},
        {"perimeter", perimeter}
        {"Perimeter", perimeter}
101010
  • 14,866
  • 30
  • 95
  • 172

2 Answers2

1

Looping over lists in preprocessor normally requires generating boilerplate macros, but you can avoid that by using a somewhat weird syntax for lists:

do_register_type(script, (area)(perimeter))

Here's how you loop over such lists:

#define REG_LOOP(seq) REG_END(REG_LOOP_A seq)

#define REG_END(...) REG_END_(__VA_ARGS__)
#define REG_END_(...) __VA_ARGS__##_END

#define REG_LOOP_A(func) REG_LOOP_BODY(func) REG_LOOP_B
#define REG_LOOP_B(func) REG_LOOP_BODY(func) REG_LOOP_A
#define REG_LOOP_A_END
#define REG_LOOP_B_END

#define REG_LOOP_BODY(func) {#func, func},

REG_LOOP((foo)(bar)) will expand to {"foo", foo}, {"bar", bar},.

Then you add this macro to do_register_type:

#define do_register_type(name, seq) \
    int do_register_ ## name(struct context *L) \
    { \
        method_type const _instance_methods[] = { \
            {"new", __new}, \
            {"delete", __delete}, \
            REG_LOOP(seq) \
            {NULL, NULL} \
        }; \
        register_type(L, name, _instance_methods, 0); \
        return 1; \
    }
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 1
    I was about to warn you to remove the __, but you just did, I hope your answer is what the OP wants (rather than mine), it is not very clear in the question ... – bruno Jul 04 '20 at 16:20
1

do you want that :

#define do_register_type(name, ...) \
    int do_register_ ## name(struct context *L) \
    { \
        method_type const _instance_methods[] = { \
            {"new", __new}, \
            {"delete", __delete}, \
            __VA_ARGS__ __VA_OPT__(,) \
            {NULL, NULL} \
        }; \
        register_type(L, name, _instance_methods, 0); \
        return 1; \
    }

do_register_type(script);

do_register_type(script, {"area", area}, {"perimeter", perimeter});

previous code being in m.c:

pi@raspberrypi:~ $ gcc -E m.c
# 1 "m.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "m.c"
# 14 "m.c"
int do_register_script(struct context *L) { method_type const _instance_methods[] = { {"new", __new}, {"delete", __delete}, {NULL, NULL} }; register_type(L, script, _instance_methods, 0); return 1; };

int do_register_script(struct context *L) { method_type const _instance_methods[] = { {"new", __new}, {"delete", __delete}, {"area", area}, {"perimeter", perimeter} , {NULL, NULL} }; register_type(L, script, _instance_methods, 0); return 1; };
pi@raspberrypi:~ $ 

I noticed you do not want the prefix '__' for area and perimeter, contrarily to new and delete but may be you want it for some other cases, because of that it is not possible to expand automatically and the only way I see is to give explicitly the code to add

bruno
  • 32,421
  • 7
  • 25
  • 37
  • Anyway to avoid that trailing comma in do_register_type? – 101010 Jul 04 '20 at 17:12
  • Your solution is working pretty well. It's clear (except for that trailing comma) how to use it. The macro isn't complex. It supports aliases. – 101010 Jul 04 '20 at 17:14
  • @010110110101 I removed it using `__VA_OPT__(,)`see my edited answer – bruno Jul 04 '20 at 17:16
  • Having a little trouble getting gcc to accept `__VA_OPT__(,)`. I tried --std=c18 and some other dialects. Any advice? – 101010 Jul 04 '20 at 17:22
  • I use *gcc* with 'forcing' a version through `-std=`, all depends on what is your gcc version and what you want, if no do not have a 'real' reason to specify the version let your compiler using its 'default' version. But I encourage you to compile using the option `-Wall` at minimum and better `-Wall -Werror -pedantic` to enforce your code – bruno Jul 04 '20 at 17:25
  • I'm definitely doing that. I have the compiler checks pretty well maxed out and the code is pretty tight about sticking to a standard. Before now, I had it set to `gnu99`. Still, no good on the `__VA_OPT__(,)` I'll keep at it. – 101010 Jul 04 '20 at 17:29
  • 1
    Got it. It was me, not the compiler dialect. – 101010 Jul 04 '20 at 17:32
  • @010110110101 if is generally us rather than the compilers ^^ – bruno Jul 04 '20 at 17:34