Disons que pour une raison quelconque, vous devez écrire une macro: MACRO(X,Y)
. (Supposons qu'il y ait une bonne raison pour laquelle vous ne pouvez pas utiliser une fonction en ligne.) Vous voulez que cette macro émule un appel à une fonction sans valeur de retour .
if (x > y)
MACRO(x, y);
do_something();
if (x > y)
MACRO(x, y);
else
MACRO(y - x, x - y);
do_something();
MACRO(x, y)
do_something();
La façon naïve d'écrire la macro est la suivante:
#define MACRO(X,Y) \
cout << "1st arg is:" << (X) << endl; \
cout << "2nd arg is:" << (Y) << endl; \
cout << "Sum is:" << ((X)+(Y)) << endl;
C'est une très mauvaise solution qui échoue aux trois exemples, et je ne devrais pas avoir besoin d'expliquer pourquoi.
Ignorer ce que fait réellement la macro, ce n'est pas le but.
Maintenant, la façon dont je vois le plus souvent les macros écrites est de les mettre entre accolades, comme ceci:
#define MACRO(X,Y) \
{ \
cout << "1st arg is:" << (X) << endl; \
cout << "2nd arg is:" << (Y) << endl; \
cout << "Sum is:" << ((X)+(Y)) << endl; \
}
Cela résout l'exemple 1, car la macro se trouve dans un bloc d'instructions. Mais l'exemple 2 est cassé car nous mettons un point-virgule après l'appel à la macro. Cela fait penser au compilateur que le point-virgule est une instruction en soi, ce qui signifie que l'instruction else ne correspond à aucune instruction if! Et enfin, l'exemple 3 compile OK, même s'il n'y a pas de point-virgule, car un bloc de code n'a pas besoin d'un point-virgule.
Existe-t-il un moyen d'écrire une macro pour qu'elle passe les trois exemples?
Remarque: je soumets ma propre réponse dans le cadre de la façon acceptée de partager un conseil , mais si quelqu'un a une meilleure solution, n'hésitez pas à la poster ici, elle peut obtenir plus de votes que ma méthode. :)
Les macros doivent généralement être évitées; leur préférez les fonctions en ligne à tout moment. Tout compilateur digne de ce nom devrait être capable d'inliner une petite fonction comme s'il s'agissait d'une macro, et une fonction en ligne respectera les espaces de noms et autres étendues, ainsi que d'évaluer tous les arguments une fois.
S'il doit s'agir d'une macro, une boucle while (déjà suggérée) fonctionnera, ou vous pouvez essayer l'opérateur virgule:
#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
Fait que l'instruction est évaluée en un type de void
, et l'utilisation de virgules plutôt que de points-virgules permet de l'utiliser à l'intérieur d'une instruction, plutôt que seulement comme autonome. Je recommanderais toujours une fonction en ligne pour une multitude de raisons, la moindre étant la portée et le fait que MACRO(a++, b++)
incrémentera a
et b
deux fois.
Il existe une solution plutôt astucieuse:
#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)
Vous disposez maintenant d'une seule instruction au niveau du bloc, qui doit être suivie d'un point-virgule. Cela se comporte comme prévu et souhaité dans les trois exemples.
Je sais que vous avez dit "ignorer ce que fait la macro", mais les gens trouveront cette question en recherchant sur la base du titre, donc je pense que la discussion d'autres techniques pour émuler des fonctions avec des macros est justifiée.
Le plus proche que je connaisse est:
#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)
Cela fait ce qui suit:
Cependant, il diffère toujours d'une fonction en ce que:
Voici une réponse venant directement du libc6
! En jetant un œil à /usr/include/x86_64-linux-gnu/bits/byteswap.h
, J'ai trouvé l'astuce que vous cherchiez.
Quelques critiques des solutions précédentes:
auto
, c'est bien, mais n'hésitez pas à utiliser le type connu/attendu à la place.L'astuce consiste à utiliser à la fois la construction (expr,expr)
Et une portée {}
:
#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; \
} \
)
Notez l'utilisation du mot clé register
, ce n'est qu'un indice pour le compilateur. Les paramètres de macro X
et Y
sont (déjà) entourés de parenthèses et convertis en un type attendu. Cette solution fonctionne correctement avec pré et post-incrémentation car les paramètres ne sont évalués qu'une seule fois.
Pour l'exemple, même si cela n'est pas demandé, j'ai ajouté l'instruction __x + __y;
, Qui est le moyen de faire évaluer le bloc entier comme cette expression précise.
Il est plus sûr d'utiliser void();
si vous voulez vous assurer que la macro n'évaluera pas une expression, ce qui est illégal là où un rvalue
est attendu.
Cependant , la solution est pas conforme à ISO C++ comme se plaindra g++ -pedantic
:
warning: ISO C++ forbids braced-groups within expressions [-pedantic]
Afin de donner du repos à g++
, Utilisez (__extension__ OLD_WHOLE_MACRO_CONTENT_HERE)
Pour que la nouvelle définition se lise:
#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; \
} \
))
Afin d'améliorer encore plus ma solution, utilisons le mot-clé __typeof__
, Comme vu dans MIN et MAX en 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; \
} \
))
Le compilateur va maintenant déterminer le type approprié. C'est aussi une extension gcc
.
Notez la suppression du mot clé register
, comme ce serait le cas pour l'avertissement suivant lorsqu'il est utilisé avec un type de classe:
warning: address requested for ‘__x’, which is declared ‘register’ [-Wextra]
C++ 11 nous a apporté des lambdas, qui peuvent être incroyablement utiles dans cette 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))
Vous conservez la puissance générative des macros, mais vous disposez d'une étendue confortable à partir de laquelle vous pouvez retourner tout ce que vous voulez (y compris void
). En outre, le problème de l'évaluation des paramètres de macro plusieurs fois est évité.
Créer un bloc à l'aide
#define MACRO(...) do { ... } while(false)
N'ajoutez pas de; après le temps (faux)
Votre réponse souffre du problème de l'évaluation multiple, donc (par exemple)
macro( read_int(file1), read_int(file2) );
fera quelque chose d'inattendu et probablement indésirable.
Comme d'autres l'ont mentionné, vous devez éviter les macros autant que possible. Ils sont dangereux en présence d'effets secondaires si les macro-arguments sont évalués plus d'une fois. Si vous connaissez le type des arguments (ou pouvez utiliser la fonction C++ 0x auto
), vous pouvez utiliser des temporaires pour appliquer une évaluation unique.
Autre problème: l'ordre dans lequel les évaluations multiples se produisent peut ne pas être celui que vous attendez!
Considérez ce 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;
}
Et sa sortie est compilée et exécutée sur ma machine:
X = 100, Y = 10000, X + Y = 110 X = 10, Y = 100, X + Y = 110