Как сделать макрос C ++ вести себя как функция? -- c++ поле с участием c-preprocessor пол Связанный проблема

How do I make a C++ macro behave like a function?


57
vote

проблема

русский

Допустим, по какой-то причине вам нужно написать макрос: <код> 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 сломан, потому что мы помещаем замок после вызова в макрос. Это делает компилятор считает, что Semicolon является утверждением само по себе, что означает, что оператор ELSE не соответствует какому-либо заявлению! И, наконец, пример 3 компилирования OK, даже если нет запятой, потому что код кода не нуждается в спячении.


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


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

Английский оригинал

Let's say that for some reason you need to write a macro: MACRO(X,Y). (Let's assume there's a good reason you can't use an inline function.) You want this macro to emulate a call to a function with no return value.


Example 1: This should work as expected.

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

Example 2: This should not result in a compiler error.

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

Example 3: This should not compile.

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

The naïve way to write the macro is like this:

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

This is a very bad solution which fails all three examples, and I shouldn't need to explain why.

Ignore what the macro actually does, that's not the point.


Now, the way I most often see macros written is to enclose them in curly braces, like this:

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

This solves example 1, because the macro is in one statement block. But example 2 is broken because we put a semicolon after the call to the macro. This makes the compiler think the semicolon is a statement by itself, which means the else statement doesn't correspond to any if statement! And lastly, example 3 compiles OK, even though there is no semicolon, because a code block doesn't need a semicolon.


Is there a way to write a macro so that it pass all three examples?


Note: I am submitting my own answer as part of the accepted way of sharing a tip, but if anyone has a better solution feel free to post it here, it may get more votes than my method. :)

</div
     
     
     

Список ответов

48
 
vote
vote
Лучший ответ
 

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

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

 <код> after_save2  

after_save3 заставляет оператор оценивать к одному из <кода> after_save4 типа, а также использование запятых, а не запятой, позволяет использовать его внутри оператора, а не только как автономный. Я бы все еще рекомендовал встроенную функцию для множества причин, наименее из которых имеет объем, и тот факт, что <код> after_save5 будет увеличиваться <код> after_save6 и <код> after_save7 дважды.

 

Macros should generally be avoided; prefer inline functions to them at all times. Any compiler worth its salt should be capable of inlining a small function as if it were a macro, and an inline function will respect namespaces and other scopes, as well as evaluating all the arguments once.

If it must be a macro, a while loop (already suggested) will work, or you can try the comma operator:

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

The (void)0 causes the statement to evaluate to one of void type, and the use of commas rather than semicolons allows it to be used inside a statement, rather than only as a standalone. I would still recommend an inline function for a host of reasons, the least of which being scope and the fact that MACRO(a++, b++) will increment a and b twice.

</div
 
 
         
         
48
 
vote

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

 <код> #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)   

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

 

There is a rather clever solution:

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

Now you have a single block-level statement, which must be followed by a semicolon. This behaves as expected and desired in all three examples.

</div
 
 
   
   
19
 
vote

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

ближайший я знаю:

 <код> #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)   

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

    .
  • работает правильно в каждом из указанных контекстов.
  • оценивает каждый из его аргументов ровно один раз, что является гарантированной функцией вызова функции (при условии, что в обоих случаях нет исключения в любом из этих выражений).
  • действует на любые типы, используя «Авто» от C ++ 0x. Это еще не стандартный C ++, но нет другого способа получить переменные TMP, потребовавшиеся правилом однооценки.
  • не требует вызывающего абонента, чтобы импортированные имена из пространства имен STD, которые делает оригинальный макрос, но функция не будет.

Однако он все еще отличается от функции в том, что:

    .
  • в некотором недействительном использовании он может дать разные ошибки компилятора или предупреждения.
  • Выйдет не так, если X или Y содержат использование «MACRO_TMP_1» или «MACRO_TMP_2» из окружающей среды.
  • связанные с пространством имен std вещь: функция использует свой собственный лексический контекст для поиска имен, в то время как макрос использует контекст своего сайта вызова. Там нет способа написать макрос, который ведет себя как функцию в этом отношении.
  • Это не может быть использовано в качестве возврата экспрессии функции недействительной, которая может быть недействительным выражением (например, Comma Solution). Это еще больше проблема, когда желаемый тип возврата не является пустотой, особенно при использовании в качестве Lvalue. Но решение Comma не может включать в себя использование объявлений, поскольку они утверждают, поэтому выберите один или используйте расширение GNU ({...}).
 

I know you said "ignore what the macro does", but people will find this question by searching based on the title, so I think discussion of further techniques to emulate functions with macros are warranted.

Closest I know of is:

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

This does the following:

  • Works correctly in each of the stated contexts.
  • Evaluates each of its arguments exactly once, which is a guaranteed feature of a function call (assuming in both cases no exceptions in any of those expressions).
  • Acts on any types, by use of "auto" from C++0x. This is not yet standard C++, but there's no other way to get the tmp variables necessitated by the single-evaluation rule.
  • Doesn't require the caller to have imported names from namespace std, which the original macro does, but a function would not.

However, it still differs from a function in that:

  • In some invalid uses it may give different compiler errors or warnings.
  • It goes wrong if X or Y contain uses of 'MACRO_tmp_1' or 'MACRO_tmp_2' from the surrounding scope.
  • Related to the namespace std thing: a function uses its own lexical context to look up names, whereas a macro uses the context of its call site. There's no way to write a macro that behaves like a function in this respect.
  • It can't be used as the return expression of a void function, which a void expression (such as the comma solution) can. This is even more of an issue when the desired return type is not void, especially when used as an lvalue. But the comma solution can't include using declarations, because they're statements, so pick one or use the ({ ... }) GNU extension.
</div
 
 
16
 
vote

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

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

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

Хитрость - использовать как <код> (expr,expr) конструкции и <код> {} case:

 <код> #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; , который является путем оценки всего блока в качестве точного выражения.

Это безопаснее использовать <Код> /usr/include/x86_64-linux-gnu/bits/byteswap.h0 Если вы хотите убедиться, что макрос не оценивается с выражением, тем самым незаконно, где ожидается <код> /usr/include/x86_64-linux-gnu/bits/byteswap.h1 .

Однако решение не соответствует ISO C ++, соответствует требованиям как будет жаловаться <код> /usr/include/x86_64-linux-gnu/bits/byteswap.h2 :

 <код> /usr/include/x86_64-linux-gnu/bits/byteswap.h3  

Для того, чтобы дать отдых в <код> /usr/include/x86_64-linux-gnu/bits/byteswap.h4 , используйте <код> /usr/include/x86_64-linux-gnu/bits/byteswap.h5 так, чтобы новое определение читается:

 <код> /usr/include/x86_64-linux-gnu/bits/byteswap.h6  

Для того, чтобы улучшить мое решение еще немного больше, давайте использовать <код> /usr/include/x86_64-linux-gnu/bits/byteswap.h7 ключевое слово, как видно в min and max в c :

 <код> /usr/include/x86_64-linux-gnu/bits/byteswap.h8  

Теперь компилятор определит соответствующий тип. Это тоже <код> /usr/include/x86_64-linux-gnu/bits/byteswap.h9 расширение.

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

 <код> auto1  
 

Here is an answer coming right from the libc6! Taking a look at /usr/include/x86_64-linux-gnu/bits/byteswap.h, I found the trick you were looking for.

A few critics of previous solutions:

  • Kip's solution does not permit evaluating to an expression, which is in the end often needed.
  • coppro's solution does not permit assigning a variable as the expressions are separate, but can evaluate to an expression.
  • Steve Jessop's solution uses the C++11 auto keyword, that's fine, but feel free to use the known/expected type instead.

The trick is to use both the (expr,expr) construct and a {} scope:

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

Note the use of the register keyword, it's only a hint to the compiler. The X and Y macro parameters are (already) surrounded in parenthesis and casted to an expected type. This solution works properly with pre- and post-increment as parameters are evaluated only once.

For the example purpose, even though not requested, I added the __x + __y; statement, which is the way to make the whole bloc to be evaluated as that precise expression.

It's safer to use void(); if you want to make sure the macro won't evaluate to an expression, thus being illegal where an rvalue is expected.

However, the solution is not ISO C++ compliant as will complain g++ -pedantic:

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

In order to give some rest to g++, use (__extension__ OLD_WHOLE_MACRO_CONTENT_HERE) so that the new definition reads:

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

In order to improve my solution even a bit more, let's use the __typeof__ keyword, as seen in MIN and MAX in 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;      }    )) 

Now the compiler will determine the appropriate type. This too is a gcc extension.

Note the removal of the register keyword, as it would the following warning when used with a class type:

warning: address requested for ‘__x’, which is declared ‘register’ [-Wextra] 
</div
 
 
10
 
vote

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

 <код> auto2  

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

 

C++11 brought us lambdas, which can be incredibly useful in this situation:

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

You keep the generative power of macros, but have a comfy scope from which you can return whatever you want (including void). Additionally, the issue of evaluating macro parameters multiple times is avoided.

</div
 
 
4
 
vote

Создать блок с использованием

 <код> auto4  

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

 

Create a block using

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

Do not add a ; after the while(false)

</div
 
 
3
 
vote

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

 <код> auto5  

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

 

Your answer suffers from the multiple-evaluation problem, so (eg)

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

will do something unexpected and probably unwanted.

</div
 
 
1
 
vote

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

As others have mentioned, you should avoid macros whenever possible. They are dangerous in the presence of side effects if the macro arguments are evaluated more than once. If you know the type of the arguments (or can use C++0x auto feature), you could use temporaries to enforce single evaluation.

Another problem: the order in which multiple evaluations happen may not be what you expect!

Consider this code:

#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; } 

And it's output as compiled and run on my machine:

 X=100, Y=10000, X+Y=110 X=10, Y=100, X+Y=110 
</div
 
 
   
   
-1
 
vote

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

Ваш макрос просто не хватает последнего запятой:

 <код> #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();   
 

If you're willing to adopt the practice of always using curly braces in your if statements,

Your macro would simply be missing the last semicolon:

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

Example 1: (compiles)

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

Example 2: (compiles)

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

Example 3: (doesn't compile)

do_something(); MACRO(x, y) do_something(); 
</div
 
 

Связанный проблема

1  C ++ - утечка памяти, вызванная использованием размещения New Over * этот указатель?  ( C memory leak caused by using placement new over this pointer ) 
Как известно, размещение New просто строит объект без выделения любой памяти. Также все члены в классе <Код> 5.5.10 являются объектами вместо указателей, хот...

1  STD :: CIN непосредственно к функции  ( Stdcin directly to a function ) 
Недавно я наткнулся на следующую часть кода. Я не знаю, имеет ли это какой-либо смысл, я просто пытаюсь его понять: <код> object Gender extends Enumeration ...

0  Libusb_Bulk_Transfer добавляет CRC?  ( Does libusb bulk transfer add crc ) 
Я пишу программу пользовательского интерфейса для устройства USB в C ++, используя Visual Studio 2019. Я использую библиотеку Libusb. Я хочу сделать объемную ...

2  ODBC и NLS_LANG  ( Odbc and nls lang ) 
Допустим, я создал две разные исполняемые файлы программы, например, в C ++. По какой-то причине две программы внутреннее представление текста отличаются др...

0  Проблема дизайна - создание шрифта Global (C ++, Marmalade)  ( Design issue making a font global c marmalade ) 
У меня есть проект Marmalade C ++, где встроенный в шрифте не масштабируется на экран. Чтобы справиться с этим вопросом, я делаю пользовательский шрифт, котор...

0  Ошибка: аргумент типа "void (opca_hello ::) ()" не соответствует "void * (*) (void *)"  ( Error argument of type void opca hello does not match void void ) 
Я написал очень простой код для резьбы. Поскольку я очень новый для этого, я понятия не имею об ошибке. <код> class opca_hello { public: void hello(); } v...

501  Как использовать постоянную PI в C ++  ( How to use the pi constant in c ) 
Я хочу использовать постоянные и тригонометрические функции PI в некоторой программе C ++. Я получаю тригонометрические функции с помощью <код> include <math....

2  Новый DataType, который может иметь количество до 100 цифр  ( New datatype which can have numbers upto 100 digits ) 
Примечание. Это был вопрос интервью и может не иметь фактического случая использования в настоящее время Вопрос должен был разработать класс, который может ...

1  Не можете получить программу Math C ++ для работы [дубликата]  ( Cant get math c program to work ) 
<в сторону CLASS = "S-NEWACTS S-WELTIVE__info JS-Post-New Imide MB16« Роль = «Статус»> Этот вопрос уже есть ответы здесь : ...

0  Использование аргументов Makefile без Foo =  ( Using makefile arguments without foo ) 
У меня есть makefile, который я использую для компиляции одного файла. Когда мне нужно пройти аргумент, я использую цель = TargetFile. Сценарий принимает ар...

-1  Неожиданный идентификатор ошибки - не уверен, почему (C ++)  ( Unexpected error id not sure why c ) 
IM Реализация программы C ++, по соображениям проекта оно должно быть включено в один файл, поэтому я не могу поставить то, что вы обычно в отдельном файле за...

1  Почему 64-битное целое расширение C ++ называется «долгим долгом»?  ( Why is the 64bit integer extension of c called long long ) 
В отличие от других типов: «int», "логический", "двойной" и т. Д. И даже таможенные классы, есть только одно слово. Однако только одно слово для их типа тольк...

5  Что такое "для (x: y)"?  ( What is for x y ) 
Итак, я оглянулся на межпубки о нитках, и я пришел в блог / учебную вещь о нитках, но то, что меня смущено, была эта линия, которую он использовал <код> for...

4  Преобразовать фильм в Openni * .oni видео  ( Convert movie to openni oni video ) 
the библиотека Kinect Openni использует пользовательский формат видеофайла для хранения видеороликов, которые содержат информацию RGB + D. Эти видео имеют р...

0  Как получить несколько наборов результатов с Poco :: Data?  ( How to fetch multiple result sets with pocodata ) 
Я прочитал Poco :: Руководство пользователя данных и упоминается, что Библиотека имеет поддержку нескольких наборов результатов. Существует пример для этой ...