最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c - Function-like macro in C11 _Generic - Stack Overflow

programmeradmin3浏览0评论

I would like to use _Generic to expand function-like macro instead of function in C:

#include <stdio.h>

#define VAL_V(X) X
#define VAL_P(X) *X

#define VAL(X) _Generic((X), int: VAL_V, int *: VAL_P)(X)

int main() {
  int a = 0;
  int *b = &a;

  VAL(a) = 3;

  return 0;
}

Of course this does not compile since VAL_V and VAL_P are not defined. And also I cannot put the argument X inside _Generic body.

Can you think of any workaround?

I would like to use _Generic to expand function-like macro instead of function in C:

#include <stdio.h>

#define VAL_V(X) X
#define VAL_P(X) *X

#define VAL(X) _Generic((X), int: VAL_V, int *: VAL_P)(X)

int main() {
  int a = 0;
  int *b = &a;

  VAL(a) = 3;

  return 0;
}

Of course this does not compile since VAL_V and VAL_P are not defined. And also I cannot put the argument X inside _Generic body.

Can you think of any workaround?

Share Improve this question asked Feb 7 at 15:59 MaPoMaPo 7916 silver badges15 bronze badges 3
  • 2 But VAL_V and VAL_P are defined in your example. – tstanisl Commented Feb 7 at 17:28
  • I would not use _Generic for anything. If you want polymorphism change the language to C++. Try to debug it after 3m. – 0___________ Commented Feb 7 at 17:32
  • The obvious solution is to use inline functions. – Lundin Commented yesterday
Add a comment  | 

2 Answers 2

Reset to default 2

To my understanding, you want VAL(a) = 3 to expand to a = 3, and VAL(b) = 3 to expand to *b = 3.

It's doable though a bit tricky.

Firstly, the return value of _Generic in an l-value thus its result can be written to. So expression:

int a;
_Generic(1, default: a) = 42;

is a perfectly valid C code.

However, one cannot blindly use:

#define VAL(x) _Generic(X, int: X, int*: *X)
int a;
VAL(a) = 42;

The reason is that all branches of generic selection must form valid expression. The *a is not a valid expression for int a. It is a well known and often criticized limitation of C language.

The workaround is to replace X in *X with a nested generic selection that returns X if X is an int*, otherwise it returns a dummy value.

Combining both tricks one can get:

#define VAL(X) VAL_((X))
#define VAL_(X) _Generic(X \
    ,int : X \
    ,int*: *_Generic(X, int*: X, default: (int*)0) \
  )

Works as expected. See godbolt.

I would like to use _Generic to expand function-like macro instead of function in C

You can't do this per se because _Generic is a C-language feature proper, not a preprocessor construct. Because the preprocessor does not interpret _Generic, you cannot use it to select among alternative macro names. Macro expansion is performed before the _Generic expression is parsed.

Can you think of any workaround?

My original answer, now deleted, overlooked that you wanted something that expanded to an lvalue. That does complicate things. Nevertheless, you have at least these options:

  1. Don't do this thing. That's not a workaround per se, just good advice. Even more so than for reading said values, masking pointer nature when writing them stinks. Even if you have a real-world use case that is more complex, I am having trouble envisioning anything along these lines where it is a good idea to obscure the identity of the object being accessed or to disguise the means of access.

    OR

  2. Use a type-generic macro to drive a ternary expression that ultimately yields a value equivalent to the address of the object to be written, then convert and dereference. Example:

    #include <stdint.h>
    
    #define VAL(X) (*(int *) \
        (_Generic((X), int: 1, int *: 0) \
            ? (intptr_t) &(X) \
            : (intptr_t)  (X)))
    
    int main(void) {
        int y;
        int *yp = &y;
    
        VAL(y) = 1;
        VAL(yp) = 2;
    }
    

Limitations of the example include, but are not necessarily limited to:

  • Because of the use of the & operator, it works only for macro arguments that are lvalues, and among those, only for those whose types are not register-qualified, regardless of whether the &(X) alternative is the one actually evaluated. Even in contexts where you don't write to the result.

  • Because it works by going through a pointer to the object to be accessed, it is suitable only for sets of type alternatives for which the types of all the potential objects to be accessed are mutually compatible.

  • Use of the ternary operator makes it difficult, albeit not impossible, to generalize to a larger number of alternatives. That's somewhat mooted, however, by the limitations described in the previous point.

I appreciate that example given in the question may be simplified relative to your real-world use case, but that doesn't much impact the above. That you want to make a selection based on data type seems to lock you into a type-generic expression, and that that expression needs to yield an lvalue seems to lock you into going through a pointer. That triggers the compatible types requirement, and probably also the constraints related to the & operator.

发布评论

评论列表(0)

  1. 暂无评论