Static_if в препроцессоре C99

Возможно ли реализовать static_if в C99?

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

Как я могу правильно реализовать STATIC_IF(…) здесь? В зависимости от COND аргументы должны быть переданы в MACRO1 или MACRO2, но аргументы для обоих макросов выглядят по-разному. COND является статически проверяемым, что-то вроде sizeof (…) > 42.

  • #if COND, тогда #define STATIC_IF MACRO1... не будет работать для моего использования.
  • Я не могу использовать специфические для компилятора решения.

Ответ 1

Это невозможно, потому что условие типа sizeof(something)>42 не является статическим для препроцессора. Препроцессор является чисто текстовым (в принципе, за исключением арифметики). Он не знает о C или типах.

Обратите внимание, что выражение условия в #if сильно ограничено.

Однако вы можете использовать трюки сборки. Например, у вас может быть отдельная программа, например

 // 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);
 }

тогда у вас есть правило вроде

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

в Makefile и т.д. и т.д.

Таким образом, ваш механизм сборки создаст соответствующие заголовки.

Вещи становятся намного сложнее, если вы хотите перекрестно скомпилировать!

Тогда у вас может быть #include "generated-sizes.h" в вашем заголовке, а затем код

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

Ответ 2

В вашем конкретном случае (если я правильно понимаю ваши комментарии), да, вы можете это сделать.

Вы не можете передать sizeof на что-либо в препроцессоре, поскольку препроцессор работает до того, как будет доступна информация о типе. К счастью для вас, вам не нужно sizeof подсчитывать количество аргументов в статически написанном списке (XY alert!), поэтому это не является препятствием.

Здесь одна возможная реализация с использованием макроса Order:

#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;
}

(Этот простой случай индексирует список по количеству аргументов - вероятно, не совсем то, что вы хотите сделать, но достаточно, чтобы получить эту идею. Заказ действительно обеспечивает полный спектр сложных, невалютационных структур управления - if, cond, match и т.д. - для более сложного принятия решений.)

Заказ довольно тяжелый: я предполагаю, что вы можете сделать что-то подобное с гораздо более легким и реалистично-портативным P99 (не знакомым с ним). Заказ очень хорошо работает с GCC и достаточно хорошо работает с Clang (Clang будет задыхаться от глубокой рекурсии или длинных циклов); это стандартный, но не все компиляторы.

Ответ 3

Я так не думаю, не в том смысле, о котором вы говорите.

Но: я бы просто пошел вперед и надеюсь, что оптимизирующий компилятор замечает, что условие всегда истинно (или ложно) и делает правильную вещь, т.е. оптимизирует тест.

Вам может потребоваться принудительная оптимизация, чтобы спровоцировать компилятор.

Ответ 4

Если вы можете удалить ограничение на то, чтобы придерживаться C99, есть лучшее решение этой проблемы, встроенное в язык, поскольку 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);
}

Оператор _Generic выполняет выбор времени компиляции на основе типа. Поскольку он выбирает на основе типа, он также является единственным выражением на уровне языка, которое может принимать конфликтующие типы "аргументов", поскольку его основная цель - разрешить значение правильного типа на основе входов.

Это означает, что вы можете легко использовать его для выбора между вашими двумя функциями с несовместимыми сигнатурами, потому что он полностью игнорирует тип, который не выбран путем сопоставления ввода; аргументы (применяемые к какой бы функции _Generic не возвращались) будут проверяться только на успешное совпадение.

Хотя _Generic предназначен для отправки по типам, а не значениям, любое целочисленное константное выражение может быть "превращено в" тип, используя его как размер массива. Таким образом, в вышеприведенном макросе мы создаем анонимный массив (nb это не VLA), из count либо 2 (для true), либо 1 (для false), и отправка против типа указателя на этот массив, чтобы разрешить, какой из две несовместимые функции.

В среде выполнения это, безусловно, сведено к нулю, поскольку не только условие статическое, но и альтернативный "путь выполнения" даже не будет вводить проверку и, следовательно, не может иметь код, сгенерированный для него в первую очередь.