Подтвердить что ты не робот

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

Скажем, что по какой-то причине вам нужно написать макрос: 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 компилируется в порядке, хотя точки с запятой нет, потому что блоку кода не требуется точка с запятой.


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


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

4b9b3361

Ответ 1

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

Если это макрос, цикл 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.

Ответ 2

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

#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)

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

Ответ 3

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

Ближе всего я знаю:

#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" из С++ 0x. Это еще не стандартный С++, но нет другого способа получить переменные tmp, требуемые правилом однократной оценки.
  • Не требует, чтобы вызывающий пользователь импортировал имена из пространства имен std, которые выполняет оригинальный макрос, но функция не будет.

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

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

Ответ 4

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

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

  • Решение Kip не позволяет оценивать выражение, которое в конечном итоге часто необходимо.
  • Решение coppro не позволяет назначать переменную, поскольку выражения являются отдельными, но может быть оценено в выражении.
  • Решение Steve Jessop использует ключевое слово С++ 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 (уже) окружены в круглых скобках и casted для ожидаемого типа. Это решение работает правильно с предварительным и пост-приращением, поскольку параметры оцениваются только один раз.

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

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

Однако, решение не соответствует требованиям ISO С++, так как будет жаловаться 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__, как показано в MIN и MAX в 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]

Ответ 5

С++ 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). Кроме того, исключается проблема оценки макропараметров несколько раз.

Ответ 6

Создайте блок с помощью

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

Не добавляйте a; после времени (false)

Ответ 7

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

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

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

Ответ 8

Как уже упоминалось, вы должны избегать макросов, когда это возможно. Они опасны при наличии побочных эффектов, если аргументы макроса оцениваются более одного раза. Если вы знаете тип аргументов (или можете использовать функцию С++ 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

Ответ 9

Если вы готовы принять практику всегда использовать фигурные скобки в ваших операторах 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();