Как реализовать статическое утверждение (во время компиляции) в функциональных макросах?
Существует хорошее обсуждение и множество альтернатив для случая внедрения статического утверждения в виде оператора «C» - расширение до вариантов static_assert. Проблема в случае функции типа объекта заключается в том, что я не могу найти способ (по крайней мере, с GCC9) включить static_assert как часть выражения.
Рассмотрим следующий пример, в котором я хотел бы проверить, что переданный параметр имеет зеленый или синий цвет, и хотел бы получить ошибку времени компиляции, если это ни то, ни другое. Это упрощенный пример, демонстрирующий проблему. Реальное состояние гораздо сложнее.
#include <stdbool.h>
#include <assert.h>
#include <stdio.h>
#define GREEN 1
#define YELLOW 2
#define RED 3
#define is_green(color) (color == GREEN ? true : color == YELLOW ? false : _Static_assert(!("Bad Color")))
int main(int argc, char **argv)
{
bool g = is_green(GREEN) ;
bool y = is_green(YELLOW) ;
bool r = is_green(RED) ; // Should fail
}
Я ожидаю, что приведенное выше приведет к ошибке утверждения времени компиляции - в строке с комментарием «Должен сбой». Вместо этого я получаю сообщение об ошибке 3 при каждом вызове is_green: «ожидаемое выражение до _Static_assert».
Основная проблема, по-видимому, заключается в том, что «Static_assert» не является функцией — это больше похоже на оператор, поэтому его нельзя включать в выражения — напрямую, через макрос или встроенную функцию.
Уже пробовал следующее:
(Static_assert(...), false)( { Static_assert(...), false } )Во всех случаях GCC не позволяет Static_assert быть частью выражения — будь то присваивание или вызов функции.
Текущий обходной путь - получить предупреждение о времени компиляции, поставив 0/0 вместо _Static_assert - GCC выдает ошибку времени компиляции - деление на ноль. Это сбивает с толку команду разработчиков.
#define is_green(color) (color == GREEN ? true : color == YELLOW ? false : 0/0 )
Любое предложение ?
Повторю: цель состоит в том, чтобы иметь возможность повышать утверждение времени компиляции для макросов, подобных функциям. В это время я
Яир
Вероятно, вам следует писать синий в макросе как BLUE, а не YELLOW, если вы проверяете зеленый или синий цвет. :)
Это не очень хороший пример того, как (пытаться) использовать статическое утверждение. Статические утверждения оцениваются компилятором во время компиляции; их нет в исполняемом файле. Синтаксически это объявление, точно так же, как int x = 3; является объявлением. Вам будет нелегко использовать его в выражении, особенно в функциональном макросе, который должен выдавать значение.
Это похоже на проблему XY. Какую проблему должно решить использование статических утверждений, подобных этому?
@JonathanLeffler - Я согласен, что пример не идеален. Моя лучшая попытка превратить сложные файлы заголовков/макросы в легко воспроизводимую проблему. Настоящая проблема, которую я пытаюсь решить, связана с «полиморфизмом», который был реализован на C с помощью автоматически сгенерированного кода. Первый аргумент — «OPCODE», остальные аргументы — указатели. Фактическая функция/интерпретация аргументов зависит от OPCODE. Очень подвержен ошибкам. Пытаюсь добавить некоторые проверки времени компиляции для повышения стабильности. Не весело.
Несмотря на это, я все еще думаю, что это интересная проблема, которая может иметь последствия во многих случаях.
@AndrewHenle — Пример слишком упрощен — я пытаюсь найти техническое решение для реализации. Актуальная проблема состоит в том, чтобы добавить проверки времени компиляции к существующим, устаревшим, сложным функциям со стилем func(int Operation, char *x, char *y,...) - с параметрами, приводимыми к char *. Операция является постоянной (для большинства вызовов), и я надеюсь добавить статическое утверждение, которое будет проверять аргументы во время компиляции. Надеюсь, это даст некоторую предысторию. Не могу поделиться реальным кодом.
«добавить статическое утверждение, которое будет проверять аргументы во время компиляции» Аргументы указателя? Без шансов. Указатели не являются постоянными выражениями.
@n.m.couldbeanAI Входные параметры в настоящее время используются в void *. С помощью простой макрос-обертки вокруг функции можно создавать сложные условия, используя GCC __builtin_совместимый_p для переданных параметров. Большинство из них я сделал, но я пытаюсь найти лучший способ обработки триггера сообщения об ошибке - лучше по сравнению с моим текущим подходом, который состоит в том, чтобы сделать 0/0 и получить деление на ошибку во время компиляции. В идеале осмысленное сообщение: необходимо передать struct foobar *, когда код операции=3, ...





В этом вопросе есть две части.
static_assert внутри выражения (возможно, расширенного из функционального макроса)?static_assert условно запущенным, где условие не является предобработкой (то есть не я #if или друзья)?Обычно ответ на оба этих вопроса — «вы не можете».
Однако, проявив немного наглости, вы можете.
Помните, что в языке C есть составные литералы. Составные литералы чем-то напоминают выражения приведения. У них есть тип внутри. Тип может быть struct. Структуры содержат объявления членов и... барабанную дробь... static_assert объявления внутри. Давайте проверим это!
#define SUCC ((struct {int z; static_assert(1==1);}){1}, 0)
#define FAIL ((struct {int z; static_assert(1==2);}){1}, 0)
int main()
{
int x = SUCC;
int y = FAIL; // fails
}
Демо Ура!
А как насчет условной части? Вам это не нужно. Если вы хотите утверждать, что цвет зеленый или желтый, просто утверждайте это.
#define is_green(color) ((struct {static_assert(color==GREEN||color==YELLOW, "Bad color!"); int z;}){1}, color == GREEN)
Конечно, это будет работать, только если аргументом макроса является постоянное выражение.
В своем решении вы не упомянули об операторе-запятой, который необходим для того, чтобы тип возвращаемого значения предложения был int вместо структуры. В моей среде GCC предупреждает о неиспользуемом значении в первой части запятой («вычисленное значение не используется»). Я предлагаю поставить (void) перед составным литералом, чтобы избавиться от этого.
Вам не нужно прибегать к нестандартным расширениям GNU. Предполагая, что я правильно понял ваши требования, возможно, вам нужно следующее:
/**
* Like C11's _Static_assert() except that it can be used in an expression.
*
* EXPR - The expression to check.
* MSG - The string literal of the error message to print only if EXPR evalutes
* to false.
*
* Always return true. */
#define STATIC_ASSERT_EXPR(EXPR, MSG) \
(!!sizeof( struct { static_assert ( (EXPR), MSG ); char c; } ))
Если EXPR оценено как ненулевое, sizeof вернет ненулевое значение для анонимного struct. !! преобразует его в 1, поэтому макрос расширяется до 1, или true. char c присутствует только для того, чтобы struct не был пустым.
В противном случае программа не сможет скомпилироваться из-за ошибочного утверждения.
Обратите внимание, что EXPR должно быть постоянным выражением.
Ссылка: Удобные макросы препроцессора C/C++.
static_assertне является ни функцией, ни утверждением. Это декларация. Оно не выполнено. Оно либо есть, либо его нет. Если он находится в непринятой ветви условной конструкции, будь тоif, условное выражение или что-то еще, он все равно там.