Как заставить макрос C++ вести себя как функция?

Допустим, вам по какой-то причине нужно написать макрос: MACRO(X,Y). (Предположим, есть веская причина, по которой вы не можете использовать встроенную функцию.) Вы хотите, чтобы этот макрос имитировал вызов функции без возвращаемого значения.


Пример 1: Это должно работать, как ожидалось.

if (x > y)
  MACRO(x, y);
do_something();

Пример 2: Это не должно приводить к ошибке компилятора.

if (x > y)
  MACRO(x, y);
else
  MACRO(y - x, x - y);

Пример 3: нет должен компилироваться.

do_something();
MACRO(x, y)
do_something();

Наивный способ написать макрос выглядит так:

#define MACRO(X,Y)                       \
cout << "1st arg is:" << (X) << endl;    \
cout << "2nd arg is:" << (Y) << endl;    \
cout << "Sum is:" << ((X)+(Y)) << endl;

Это очень плохое решение, которое не подходит для всех трех примеров, и мне не нужно объяснять, почему.

Не обращайте внимания на то, что на самом деле делает макрос, дело не в этом.


Чаще всего макросы заключаются в фигурные скобки, например:

#define MACRO(X,Y)                         \
{                                          \
  cout << "1st arg is:" << (X) << endl;    \
  cout << "2nd arg is:" << (Y) << endl;    \
  cout << "Sum is:" << ((X)+(Y)) << endl;  \
}

Это решает пример 1, потому что макрос находится в одном блоке операторов. Но пример 2 не работает, потому что мы ставим точку с запятой после вызова макроса. Это заставляет компилятор думать, что точка с запятой сама по себе является оператором, что означает, что оператор else не соответствует ни одному оператору if! И, наконец, пример 3 компилируется нормально, хотя точки с запятой нет, потому что для блока кода точка с запятой не нужна.


Есть ли способ написать макрос, чтобы он передавал все три примера?


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

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

b1nary.atr0phy 04.04.2013 19:16

@ b1naryatr0phy Я думаю, что беспорядок макросов больше проблема, почему не макросы.

bobobobo 16.05.2013 05:31
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
58
3
121 059
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Есть довольно умное решение:

#define MACRO(X,Y)                         \
do {                                       \
  cout << "1st arg is:" << (X) << endl;    \
  cout << "2nd arg is:" << (Y) << endl;    \
  cout << "Sum is:" << ((X)+(Y)) << endl;  \
} while (0)

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

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

Kip 03.10.2008 17:54

Между прочим, проблема множественной оценки не является непреодолимой. Проверьте этот код, который сделал умный парень -> pastie.org/357910 Он предоставляет надежный макрос «RANGE (foo)», который в основном генерирует «foo.begin (), foo.end ()», но он применим для решения любой множественной оценки. Зато накладные расходы времени выполнения.

Iraimbilanja 11.01.2009 16:57

Создайте блок, используя

 #define MACRO(...) do { ... } while(false)

Не добавляйте; через некоторое время (ложь)

Если вы готовы принять практику использования фигурных скобок в операторах if,

В вашем макросе просто не будет последней точки с запятой:

#define MACRO(X,Y)                       \
cout << "1st arg is:" << (X) << endl;    \
cout << "2nd arg is:" << (Y) << endl;    \
cout << "Sum is:" << ((X)+(Y)) << endl

Пример 1: (компилируется)

if (x > y) {
    MACRO(x, y);
}
do_something();

Пример 2: (компилируется)

if (x > y) {
    MACRO(x, y);
} else {
    MACRO(y - x, x - y);
}

Пример 3: (не компилируется)

do_something();
MACRO(x, y)
do_something();
Ответ принят как подходящий

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

Если это должен быть макрос, цикл while (уже предложенный) будет работать, или вы можете попробовать оператор запятой:

#define MACRO(X,Y) \
 ( \
  (cout << "1st arg is:" << (X) << endl), \
  (cout << "2nd arg is:" << (Y) << endl), \
  (cout << "3rd arg is:" << ((X) + (Y)) << endl), \
  (void)0 \
 )

(void)0 заставляет оператор оценивать один из типов void, а использование запятых, а не точек с запятой позволяет использовать его внутри оператора, а не только как отдельный. Я бы по-прежнему рекомендовал встроенную функцию по множеству причин, наименьшая из которых - это область действия и тот факт, что MACRO(a++, b++) будет увеличивать a и b вдвое.

Отлично, это даже лучше, чем то, что я делал изначально! Спасибо!

Kip 02.10.2008 21:08

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

Mike F 02.10.2008 21:24

«Отлично, это даже лучше, чем то, что я делал изначально!» - Ты ошибаешься, Кип, твой ответ лучше, чем этот. Ваш может вести себя как другие функции, чем этот, например, если ему нужен цикл в середине.

Windows programmer 03.10.2008 04:09

Кип, твоя версия лучше. Если вам нужно объявить некоторые локальные переменные в вашем макросе, объявление их внутри блока while не приведет к утечке их в вызывающую область.

Vincent Robert 05.10.2008 14:56

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

Trevor Robinson 18.01.2013 21:47

Ваш ответ страдает от проблемы множественной оценки, поэтому (например)

macro( read_int(file1), read_int(file2) );

сделает что-то неожиданное и, вероятно, нежелательное.

Как уже упоминали другие, вам следует по возможности избегать макросов. Они опасны при наличии побочных эффектов, если аргументы макроса оцениваются более одного раза. Если вам известен тип аргументов (или вы можете использовать функцию C++ 0x auto), вы можете использовать временные файлы для принудительной однократной оценки.

Еще одна проблема: порядок, в котором выполняются несколько оценок, может быть не таким, как вы ожидаете!

Рассмотрим этот код:

#include <iostream>
using namespace std;

int foo( int & i ) { return i *= 10; }
int bar( int & i ) { return i *= 100; }

#define BADMACRO( X, Y ) do { \
    cout << "X = " << (X) << ", Y = " << (Y) << ", X+Y = " << ((X)+(Y)) << endl; \
    } while (0)

#define MACRO( X, Y ) do { \
    int x = X; int y = Y; \
    cout << "X = " << x << ", Y = " << y << ", X+Y = " << ( x + y ) << endl; \
    } while (0)

int main() {
    int a = 1; int b = 1;
    BADMACRO( foo(a), bar(b) );
    a = 1; b = 1;
    MACRO( foo(a), bar(b) );
    return 0;
}

И он выводится как скомпилированный и запускаемый на моей машине:

X=100, Y=10000, X+Y=110
X=10, Y=100, X+Y=110

Проблема с вашим хорошим макросом в том, что вы предполагаете, что X и Y являются int. Они могли быть кем угодно. Вам нужно будет создать шаблон макроса или что-то в этом роде.

Tanktalus 02.10.2008 23:13

Примечание: выходы на Godbolt не соответствуют тем, которые указаны в ответе: godbolt.org/z/9fQuA9

nz_21 03.05.2020 17:43

Я знаю, что вы сказали «игнорировать то, что делает макрос», но люди найдут этот вопрос, выполнив поиск по названию, поэтому я думаю, что обсуждение дальнейших методов эмуляции функций с помощью макросов оправдано.

Ближайшее, что я знаю, это:

#define MACRO(X,Y) \
do { \
    auto MACRO_tmp_1 = (X); \
    auto MACRO_tmp_2 = (Y); \
    using std::cout; \
    using std::endl; \
    cout << "1st arg is:" << (MACRO_tmp_1) << endl;    \
    cout << "2nd arg is:" << (MACRO_tmp_2) << endl;    \
    cout << "Sum is:" << (MACRO_tmp_1 + MACRO_tmp_2) << endl; \
} while(0)

Это делает следующее:

  • Правильно работает в каждом из указанных контекстов.
  • Оценивает каждый из своих аргументов ровно один раз, что является гарантированной функцией вызова функции (при условии, что в обоих случаях нет исключений ни в одном из этих выражений).
  • Действует на любые типы, используя «auto» из C++ 0x. Это еще не стандарт C++, но нет другого способа получить переменные tmp, необходимые для правила однократной оценки.
  • Не требует, чтобы вызывающий объект имел импортированные имена из пространства имен std, как это делает исходный макрос, но функция этого не сделает.

Однако он по-прежнему отличается от функции тем, что:

  • В некоторых случаях недопустимого использования он может выдавать разные ошибки компилятора или предупреждения.
  • Неправильно, если X или Y содержат использование MACRO_tmp_1 или MACRO_tmp_2 из окружающей области.
  • Связано с пространством имен std: функция использует свой собственный лексический контекст для поиска имен, тогда как макрос использует контекст своего сайта вызова. Невозможно написать макрос, который в этом отношении ведет себя как функция.
  • Его нельзя использовать в качестве возвращаемого выражения функции void, в отличие от выражения void (например, решения с запятой). Это еще более серьезная проблема, когда желаемый тип возвращаемого значения не является недействительным, особенно при использовании в качестве lvalue. Но решение с запятыми не может включать в себя объявления using, поскольку они являются операторами, поэтому выберите одно или используйте расширение GNU ({...}).

Вот ответ прямо от libc6! Взглянув на /usr/include/x86_64-linux-gnu/bits/byteswap.h, я нашел уловку, которую вы искали.

Несколько критиков предыдущих решений:

  • Решение Кипа не позволяет использовать оценка выражения, который, в конце концов, часто требуется.
  • Решение coppro не разрешает присвоение переменной, поскольку выражения являются отдельными, но может оценивать выражение.
  • В решении Стива Джессопа используется ключевое слово C++ 11 auto, это нормально, но вместо этого используется не стесняйтесь использовать известный / ожидаемый тип.

Уловка состоит в том, чтобы использовать как конструкцию (expr,expr), так и область видимости {}:

#define MACRO(X,Y) \
  ( \
    { \
      register int __x = static_cast<int>(X), __y = static_cast<int>(Y); \
      std::cout << "1st arg is:" << __x << std::endl; \
      std::cout << "2nd arg is:" << __y << std::endl; \
      std::cout << "Sum is:" << (__x + __y) << std::endl; \
      __x + __y; \
    } \
  )

Обратите внимание на использование ключевого слова register, это всего лишь подсказка для компилятора. Параметры макроса X и Y (уже) заключены в круглые скобки и отлитый для ожидаемого типа. Это решение правильно работает с пре- и пост-инкрементом, поскольку параметры оцениваются только один раз.

В качестве примера, даже если он не запрошен, я добавил оператор __x + __y;, который позволяет оценить весь блок как это точное выражение.

Безопаснее использовать void();, если вы хотите быть уверенным, что макрос не будет вычислять выражение, что является недопустимым там, где ожидается rvalue.

тем не мение, решение - не соответствует ISO C++, как будет жаловаться g++ -pedantic:

warning: ISO C++ forbids braced-groups within expressions [-pedantic]

Чтобы дать g++ немного отдохнуть, используйте (__extension__ OLD_WHOLE_MACRO_CONTENT_HERE), чтобы новое определение гласило:

#define MACRO(X,Y) \
  (__extension__ ( \
    { \
      register int __x = static_cast<int>(X), __y = static_cast<int>(Y); \
      std::cout << "1st arg is:" << __x << std::endl; \
      std::cout << "2nd arg is:" << __y << std::endl; \
      std::cout << "Sum is:" << (__x + __y) << std::endl; \
      __x + __y; \
    } \
  ))

Чтобы еще немного улучшить мое решение, давайте воспользуемся ключевым словом __typeof__, как показано в МИН и МАКС в C:

#define MACRO(X,Y) \
  (__extension__ ( \
    { \
      __typeof__(X) __x = (X); \
      __typeof__(Y) __y = (Y); \
      std::cout << "1st arg is:" << __x << std::endl; \
      std::cout << "2nd arg is:" << __y << std::endl; \
      std::cout << "Sum is:" << (__x + __y) << std::endl; \
      __x + __y; \
    } \
  ))

Теперь компилятор определит подходящий тип. Это тоже расширение gcc.

Обратите внимание на удаление ключевого слова register, так как это будет следующее предупреждение при использовании с типом класса:

warning: address requested for ‘__x’, which is declared ‘register’ [-Wextra]

В C++ 11 появились лямбды, которые могут быть невероятно полезны в этой ситуации:

#define MACRO(X,Y)                              \
    [&](x_, y_) {                               \
        cout << "1st arg is:" << x_ << endl;    \
        cout << "2nd arg is:" << y_ << endl;    \
        cout << "Sum is:" << (x_ + y_) << endl; \
    }((X), (Y))

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

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