Статическое утверждение в функциональном макросе C

Как реализовать статическое утверждение (во время компиляции) в функциональных макросах?

Существует хорошее обсуждение и множество альтернатив для случая внедрения статического утверждения в виде оператора «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)
  • Используйте выражения операторов GCC ( { Static_assert(...), false } )
  • Используйте GCC __builtin_choose_expr

Во всех случаях GCC не позволяет Static_assert быть частью выражения — будь то присваивание или вызов функции.

Текущий обходной путь - получить предупреждение о времени компиляции, поставив 0/0 вместо _Static_assert - GCC выдает ошибку времени компиляции - деление на ноль. Это сбивает с толку команду разработчиков.

#define is_green(color) (color == GREEN ? true : color == YELLOW ? false : 0/0 )

Любое предложение ?

Повторю: цель состоит в том, чтобы иметь возможность повышать утверждение времени компиляции для макросов, подобных функциям. В это время я

Яир

static_assert не является ни функцией, ни утверждением. Это декларация. Оно не выполнено. Оно либо есть, либо его нет. Если он находится в непринятой ветви условной конструкции, будь то if, условное выражение или что-то еще, он все равно там.
n. m. could be an AI 08.06.2024 16:22

Вероятно, вам следует писать синий в макросе как BLUE, а не YELLOW, если вы проверяете зеленый или синий цвет. :)

Jonathan Leffler 08.06.2024 16:52

Это не очень хороший пример того, как (пытаться) использовать статическое утверждение. Статические утверждения оцениваются компилятором во время компиляции; их нет в исполняемом файле. Синтаксически это объявление, точно так же, как int x = 3; является объявлением. Вам будет нелегко использовать его в выражении, особенно в функциональном макросе, который должен выдавать значение.

Jonathan Leffler 08.06.2024 17:03

Это похоже на проблему XY. Какую проблему должно решить использование статических утверждений, подобных этому?

Andrew Henle 08.06.2024 18:26

@JonathanLeffler - Я согласен, что пример не идеален. Моя лучшая попытка превратить сложные файлы заголовков/макросы в легко воспроизводимую проблему. Настоящая проблема, которую я пытаюсь решить, связана с «полиморфизмом», который был реализован на C с помощью автоматически сгенерированного кода. Первый аргумент — «OPCODE», остальные аргументы — указатели. Фактическая функция/интерпретация аргументов зависит от OPCODE. Очень подвержен ошибкам. Пытаюсь добавить некоторые проверки времени компиляции для повышения стабильности. Не весело.

dash-o 08.06.2024 18:27

Несмотря на это, я все еще думаю, что это интересная проблема, которая может иметь последствия во многих случаях.

dash-o 08.06.2024 18:28

@AndrewHenle — Пример слишком упрощен — я пытаюсь найти техническое решение для реализации. Актуальная проблема состоит в том, чтобы добавить проверки времени компиляции к существующим, устаревшим, сложным функциям со стилем func(int Operation, char *x, char *y,...) - с параметрами, приводимыми к char *. Операция является постоянной (для большинства вызовов), и я надеюсь добавить статическое утверждение, которое будет проверять аргументы во время компиляции. Надеюсь, это даст некоторую предысторию. Не могу поделиться реальным кодом.

dash-o 08.06.2024 18:57

«добавить статическое утверждение, которое будет проверять аргументы во время компиляции» Аргументы указателя? Без шансов. Указатели не являются постоянными выражениями.

n. m. could be an AI 08.06.2024 19:43

@n.m.couldbeanAI Входные параметры в настоящее время используются в void *. С помощью простой макрос-обертки вокруг функции можно создавать сложные условия, используя GCC __builtin_совместимый_p для переданных параметров. Большинство из них я сделал, но я пытаюсь найти лучший способ обработки триггера сообщения об ошибке - лучше по сравнению с моим текущим подходом, который состоит в том, чтобы сделать 0/0 и получить деление на ошибку во время компиляции. В идеале осмысленное сообщение: необходимо передать struct foobar *, когда код операции=3, ...

dash-o 08.06.2024 20:06
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
9
109
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

В этом вопросе есть две части.

  1. Как разместить объявление типа static_assert внутри выражения (возможно, расширенного из функционального макроса)?
  2. Как сделать 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) перед составным литералом, чтобы избавиться от этого.

stefanct 12.06.2024 19:19

Вам не нужно прибегать к нестандартным расширениям 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++.

Другие вопросы по теме