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

StackOverflow https://stackoverflow.com/questions/163365

  •  03-07-2019
  •  | 
  •  

Вопрос

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


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


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

Это было полезно?

Решение

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

Если это должен быть макрос, будет работать цикл 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 дважды.

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

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

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

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

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

Самое близкое, о чем я знаю, это:

#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-выражения (такого как решение с запятой).Это еще большая проблема, когда желаемый возвращаемый тип не является void , особенно при использовании в качестве lvalue .Но решение с запятой не может включать использование объявлений, потому что они являются операторами, поэтому выберите одно из них или используйте ({ ...}) Расширение 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 ). Кроме того, можно избежать многократной оценки параметров макроса.

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

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

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

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

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

Если вы хотите использовать практику использования фигурных скобок в своих операторах 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();
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top