Как проверить` typeof ' для значения void во время компиляции?

предположим, что я хочу иметь макрос C, который работает на любом типе. Я использую компилятор GCC (>= 4.6) и могу использовать макросы GNU99.

//code...
any_type_t *retVal = function_that_runs_very_long_time(a, b, &&c, **d, &e, *f);
//other code...

использование макроса для таймера может выглядеть, например, так

//code...
any_type_t *retVal = 
    TIMER(
          function_that_runs_very_long_time(a, b, &&c, **d, &e, *f),
          "TIMING FOR VALUE <%d, %d>", a, b
         );
//other code...

таким образом, таймер должен возвращать значение данной функции и печатать продолжительность ее запуска. Существует проблема с функциями, которые имеют void тип возвращаемого значения.

Я, очевидно, могу иметь два макроса, такие как TIMER_TYPE и TIMER_VOID, но я хочу использовать один для функция с возвращаемым значением.

Спасибо за предложения.


отредактированный пример этого макроса таймера

#define TIMER(expr, fmt_msg, ...)                           
({                                                          
    struct timeval before, after;                           
    uint64_t time_span;                                     
    int time_span_sec, time_span_usec;                      
    gettimeofday(&before, NULL);                            
    typeof(expr) _timer_expr__ = (expr);                     // <- static if?
    gettimeofday(&after, NULL);                             
    time_span = (after.tv_sec * 1000000 + after.tv_usec)    
              - (before.tv_sec * 1000000 + before.tv_usec); 
    time_span_sec  = time_span / 1000000;                   
    time_span_usec = time_span % 1000000;                   
    TRACE(fmt_msg "n%s : %d.%d seconds",                   
          #expr, time_span_sec, time_span_usec, ...);       
    _timer_expr__;                                          
})

3 ответов


какой интересный вопрос, спасибо!

после нескольких экспериментов, я нашел решение, которое использует __builtin_types_compatible_p и __builtin_choose_expr внутренние компоненты GCC.

__builtin_types_compatible_p

цитирование руководства GCC:

встроенные функции: int __builtin_types_compatible_p (type1, type2)

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

эта встроенная функция возвращает значение 1 если версии типов type1 и type2 (которые являются типами, а не выражения) совместимы, 0 иначе. результат этой встроенной функции можно использовать в целочисленных постоянных выражениях.

эта встроенная функция игнорирует квалификаторы верхнего уровня (например,const, volatile). Например, int эквивалентно const int.

Итак, вот как мы можем проверить "voidness".

#define __type_is_void(expr) __builtin_types_compatible_p(typeof(expr), void)

__builtin_choose_expr

встроенные функции: type __builtin_choose_expr (const_exp, exp1, exp2)

вы можете использовать встроенную функцию __builtin_choose_expr для оценки кода в зависимости от значения постоянного выражения. Эта встроенная функция возвращает exp1 если const_exp, которое является целочисленным постоянным выражением, не равно нулю. В противном случае он возвращает exp2.

эта встроенная функция аналогична ? : оператор в C, за исключением того, что возвращаемое выражение имеет свой тип, не измененный правилами продвижения. Кроме того, встроенная функция не оценивает выражение, которое не выбрано. Например, если const_exp значение true, exp2 не оценивается, даже если он имеет побочные эффекты.

если exp1 возвращается, тип возврата совпадает с 'ы. Аналогично, если exp2 возвращается, его тип возврата совпадает с exp2.

так __builtin_choose_expr intrinsic-это что-то вроде "статического переключателя", оцениваемого во время компиляции.

подготовка

я не вставляю сюда ваш TIMER макрос, но я предполагаю, что он может разделить его на две версии: одна для void expr и один для остальных. Вот только заглушки, которые оценивают выражение и дают результат того же типа.

#define __DO(expr) \
    ({ typeof(expr) __ret; __ret = (expr); __ret; })

#define __DO_VOID(expr) \
    (void) (expr)

наивное решение

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

#define DO(expr) \
    __builtin_choose_expr(__type_is_void(expr), \
        __DO_VOID(expr), \
        __DO(expr))  # won't work

попытка скомпилировать этот код, передавая выражение void, дает следующую ошибку:

test.c:28:9: error: variable or field ‘__ret’ declared void
test.c:28:9: error: void value not ignored as it ought to be

хотя __DO_VOID выбирается, __DO выдает ошибки. Это поведение описано в руководстве:

... неиспользуемое выражение (exp1 или exp2 в зависимости от значения const_exp) может по-прежнему генерировать синтаксические ошибки. Это может измениться в будущие изменения.

рабочего раствора

фокус в том, чтобы заменить исходную пустоту expr С некоторым значением Non-void, чтобы иметь возможность компилировать __DO case (который в любом случае является мертвым кодом, когда expr является ничтожным).

#define __expr_or_zero(expr) __builtin_choose_expr(__type_is_void(expr), 0, (expr))

#define DO(expr) \
    __builtin_choose_expr(__type_is_void(expr), \
        __DO_VOID(expr), \
        __DO(__expr_or_zero(expr))) # works fine!

вот именно! Вот полный исходный код Ideone:http://ideone.com/EFy4pE


можете ли вы принять ответ "это не возможно" ?

не та часть, о возвращении из макроса. но часть об условном тестировании expr для его возвращаемого типа.

по сути, вы просите что-то вроде следующего:

скажем, вместо какой-то магической проверки под названием "is_expr_type_void(expr)", вы просто передаете 1 или 0 во время вызова, чтобы указать is_void или !is_void в следующем варианте макрос:

#define TIMER(is_void, expr, fmt_msg, ...)                  \
({                                                          \
    struct timeval before, after;                           \
    uint64_t time_span;                                     \
    int time_span_sec, time_span_usec;                      \
    gettimeofday(&before, NULL);                            \
    if (is_void)                                            \
        (expr)                                              \
    else                                                    \
        typeof(expr) _timer_expr__ = (expr);                \ // <- static if?
    gettimeofday(&after, NULL);                             \
    time_span = (after.tv_sec * 1000000 + after.tv_usec)    \
              - (before.tv_sec * 1000000 + before.tv_usec); \
    time_span_sec  = time_span / 1000000;                   \
    time_span_usec = time_span % 1000000;                   \
    TRACE(fmt_msg "\n%s : %d.%d seconds",                   \
          #expr, time_span_sec, time_span_usec, ...);       \
    if (!is_void)                                           \
        _timer_expr__;                                      \
})

этого просто не может работать. препроцессор создаст код для этого условного if-else во всех случаях, как void, так и Non-void вызовов функций. и обе стороны будут компилировать штраф для непустых функций. но компилятор всегда будет подавляться" другой " частью условного, когда таймер вызывается с помощью функции void ... несмотря на то, что код никогда не будет вызван.

(теперь, если бы существовал действительно умный компилятор, который мог бы идентифицировать что это будет мертвый код и мертвая полоса, прежде чем помечать его как ошибку времени компиляции, Вам ПОВЕЗЕТ! но я не думаю, что gcc 4.6 настолько умен ...)

это оставляет вас с тем, что было бы предпочтительным вариантом условия #if (is_void) внутри #define. но это просто запрещено. с тех пор, как ответ указывает, что при попытке ответить на аналогичный вопрос об условной предварительной обработке препроцессор не является turing-complete.

Итак ... несмотря на ваше желание иметь один макрос, я думаю, что ваш самый простой ответ-создать один для функций void и один для функций с возвращаемыми значениями.


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