Unmanned Player
Unmanned Player

Reputation: 1209

C11 _Generic evaluating another macro

I'd like to make _Generic evaluate another macro. In the example below, db_put_u8 will insert a uint8_t into the buffer and while at it, it will also leave a log message. The log helps me understand if I missed a field. Hence the macro. I have reduced the functionality to illustrate the problem here, but in practice it's supposed to have for various data types.

#include <stdio.h>
#include <stdint.h>

#define db_put_u8(con, name, val) \
    do { \
        fputs(name "<- " #val, con); \
    } while(0)

#define db_put(con, name, val) _Generic(val, uint8_t: db_put_u8)(con, name, val)

int main(void)
{
    uint8_t v = 10;
    db_put(stdout, "some-field", v);
    return 0;
}

When I try compiling this, I get an error:

test.c:15:5: error: use of undeclared identifier 'db_put_u8'
    db_put(stdout, "some-field", v);
    ^
test.c:9:55: note: expanded from macro 'db_put'
#define db_put(con, name, val) _Generic(val, uint8_t: db_put_u8)(con, name, val)
                                                      ^
1 error generated.

Am I doing _Generic right? The generic selection syntax does not explicitly restrict calling macros in it. Can someone explain what I am doing wrong and how to fix it?

Upvotes: 0

Views: 1050

Answers (2)

mediocrevegetable1
mediocrevegetable1

Reputation: 4217

This line:

#define db_put(con, name, val) _Generic(val, uint8_t: db_put_u8)(con, name, val)

First of all has to change to:

#define db_put(con, name, val) _Generic(val, uint8_t: db_put_u8(con, name, val))

Because _Generic works at compile time, not preprocessor time. And after preprocessing, db_put_u8 will not expand because it doesn't exist as a macro. db_put_u8(arg1, arg2, arg3), however, does, so you have to put the brackets in the generic. However, there's another error then: _Generic expects an eression, so a do while loop would not work. In this case though, you don't even need to encase your macro in a loop because it's just one statement, so it's free from the potential issues of macros with many statements. Your final program should look something like this:

#include <stdio.h>
#include <stdint.h>

#define db_put_u8(con, name, val) \
        fputs(name "<- " #val, con)

#define db_put(con, name, val) _Generic(val, uint8_t: db_put_u8(con, name, val))

int main(void)
{
    uint8_t v = 10;
    db_put(stdout, "some-field", v);
    return 0;
}

EDIT: In response to the comment, there is a solution. So long as the second thing to do in db_put_u8 is also a statement, you could use the comma operator and a few more brackets. Here's an example program to put it to the test:

#include <stdio.h>
#include <stdint.h>

#define db_put_u8(con, name, val) \
   (fputs(name "<- " #val, con), \
    fputs("\nAnother test line\n", con))

#define db_put(con, name, val) \
    (_Generic(val, uint8_t: db_put_u8(con, name, val)))

int main(void)
{
    uint8_t v = 10;
    db_put(stdout, "some-field", v);
    return 0;
}

When I run this, it outputs:

some-field<- v
Another test line

Upvotes: 3

tstanisl
tstanisl

Reputation: 14137

Macro db_put_u8 would expand as do ... while(...) statement which is not an expression. _Generic expects expression thus error is observed. You should provide a function named db_put_u8. The function name decays to pointer to the function which is an an expression.

Other way around is to replace db_put_u8 with 'fputs(name "<- " #val, con)' inside _Generic and remove final '(name, val,con)'

    #define db_put(con, name, val) \
        _Generic(val, uint8_t: fputs(name "<- " #val, con))

If db_put_u8 cannot be changed then one could use an C extension named statement expression. It is supported by GCC and CLANG. Basically it allows to put arbitrary statement info ({ ... }). The last expression evaluated is returned as result.

    #define db_put(con, name, val) \
        _Generic(val, uint8_t: ({db_put_u8(con, name, val);}))

Upvotes: 0

Related Questions