سؤال

Is it possible to implement static_if in C99?

#define STATIC_IF(COND, ...) \
     if (COND) MACRO1(__VA_ARGS__); \
     else MACRO2(__VA_ARGS__);

How can I properly implement STATIC_IF(…) in here? Depending on COND the arguments either should be passed to MACRO1 or MACRO2, but the arguments for both macros look differently. COND is statically testable, something like sizeof (…) > 42.

  • #if COND then #define STATIC_IF MACRO1 … wouldn't work for my use case.
  • I cannot use compiler specific solutions.
هل كانت مفيدة؟

المحلول 2

This is not possible, because a condition like sizeof(something)>42 is not static for the preprocessor. The preprocessor is purely textual (in principle, except for arithmetic). It does not know about C or types.

Notice that expression of the condition in #if is severely constrained.

However, you could use build tricks. For instance, you might have a standalone program like

 // generate-sizeof.c
 #include <stdio.h>
 #include "foo-header.h"

 int main(int argc, char**argv) {
    const char* headername = NULL;
    if (argc<2) 
      { fprintf(stderr, "%s: missing header name\n", argv[0]); 
        exit(EXIT_FAILURE); };
    headername = argv[1]; 
    FILE *fh = fopen(headername, "w");
    if (!fh) { perror(headername); exit(EXIT_FAILURE); };
    fprintf(fp, "// generated file %s\n", headername);
    fprintf(fp, "#define SIZEOF_charptr %d\n", (int) sizeof(char*));
    fprintf(fp, "#define SIZEOF_Foo %d\n", (int) sizeof(Foo));
    fclose (fp);
 }

then have a rule like

 generated-sizes.h : generate-sizeof foo-header.h
     ./generate-sizeof generated-sizes.h

in your Makefile etc etc...

So your build machinery will generate the appropriate headers.

Things become much tricker if you want to cross-compile!

Then you might have an #include "generated-sizes.h" in your header, and later code

#if SIZEOF_Foo > 42
#error cannot have such big Foo
#endif

نصائح أخرى

In your specific case (if I understand your comments correctly), yes, you can do this.

You can't pass sizeof to anything in the preprocessor because the preprocessor runs before type information is available. Luckily for you, you don't need sizeof to count the number of arguments in a statically-written list (X-Y alert!), so this is no obstacle.

Here's one possible implementation using the Order macro library:

#include <stdio.h>
#include <order/interpreter.h>

void oneArg(int a) {
    printf("one arg: %d\n", a);
}

void twoArgs(int a, int b) {
    printf("two args: %d %d\n", a, b);
}

void threeArgs(int a, int b, int c) {
    printf("three args: %d %d %d\n", a, b, c);
}

#define ORDER_PP_DEF_8function_list  \
ORDER_PP_CONST(("unused")            \
               (oneArg)              \
               (twoArgs)             \
               (threeArgs))

#define SelectFunction(...) ORDER_PP (                                 \
    8seq_at(8tuple_size(8((__VA_ARGS__))), 8function_list)  \
)

#define Overloaded(...) SelectFunction(__VA_ARGS__)(__VA_ARGS__)

int main(void) {
    Overloaded(42);
    Overloaded(42, 47);
    Overloaded(42, 47, 64);
    return 0;
}

(This simple case indexes a list by the number of arguments - probably not exactly what you want to do, but enough to get the idea. Order does provide a full range of complex, nonevaluating control structures - if, cond, match, etc. - for more complex decision-making.)

Order is pretty heavyweight: I assume you can do something similar with the much lighter and more realistically-portable P99 (not familiar with it). Order works very well with GCC and adequately well with Clang (Clang will choke on deep recursion or long loops); it is standard, but not all compilers are.

I don't think so, not in the sense you mean.

But: I would just go ahead, and trust that an optimizing compiler notices that the condition is always true (or false) and does the right thing, i.e. optimizes out the test.

You might need to force some optimization to provoke the compiler into doing this.

If you can remove the restriction of having to stick to C99, there is a better solution to this problem built-in to the language since C11:

#include <stdio.h>

void f1(float x, double y, float * z) {
  printf("inside f1\n");
}

void f2(int x, _Bool * y) {
  printf("inside f2\n");
}

#define STATIC_IF(COND, ...) _Generic(&(int[(!!(COND))+1]){ 0 }, \
    int(*)[2]: f1, \
    int(*)[1]: f2) \
  (__VA_ARGS__)


int main(void) {
  float fl;
  _Bool b;

  STATIC_IF(sizeof(double) > 4, 0.0f, 1.0, &fl);
  STATIC_IF(sizeof(double) > 128, 16, &b);
}

The _Generic operator performs a compile-time selection based on a type. Since it selects based on a type, it's also the only language-level expression that can accept conflicting types of "argument", since its very purpose is to resolve a value of the right type based on inputs.

This means you can easily use it to choose between your two functions with incompatible signatures, because it will completely ignore the type of the one that isn't chosen by matching the input; the arguments (applied to whichever function _Generic returns) will only be checked against the successful match.

Although _Generic is designed to dispatch on types, not values, any integer constant expression can be "turned into" a type by using it as the size of an array. So in the above macro we create an anonymous array (n.b. this is not a VLA), of count either 2 (for true) or 1 (for false) and dispatch against the type of the pointer to that array in order to resolve which of the two incompatible functions to use.

This will certainly reduce to nothing at runtime, since not only is the condition static, but the alternative "execution path" wouldn't even type check and thus can't have code generated for it in the first place.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top