Как проверить` 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
.
Итак, вот как мы можем проверить "void
ness".
#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 и один для функций с возвращаемыми значениями.