28 votes

Comment puis-je faire en sorte qu'une macro CmD se comporte comme une fonction ?

Imaginons que, pour une raison quelconque vous avez besoin d'écrire une macro: MACRO(X,Y). (Imaginons qu'il y a une bonne raison pour laquelle vous ne pouvez pas utiliser une fonction en ligne.) Vous voulez que cette macro pour émuler un appel à une fonction sans valeur de retour.


Exemple 1: Cela devrait fonctionner comme prévu.

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

Exemple 2: Cela ne devrait pas entraîner une erreur de compilation.

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

Exemple 3: Ce ne devrait pas compiler.

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


Le naïf façon d'écrire la macro est comme ceci:

#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 ne satisfait pas toutes les trois exemples, et je ne devrais pas besoin d'expliquer pourquoi.

Ignorer ce que la macro en fait, ce n'est pas le point.


Maintenant, la façon dont j'ai le plus souvent voir des macros écrites est de les enfermer dans des 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, parce que la macro est dans un bloc d'instructions. Mais l'exemple 2 est cassé parce que nous avons mis un point-virgule après l'appel à la macro. Cela rend le compilateur pense que le point-virgule est une déclaration faite par lui-même, ce qui signifie que le else ne correspond à aucune instruction si! Et enfin, l'exemple 3 compile OK, même si il n'y a pas de point-virgule, car un bloc de code n'a pas besoin d'un point-virgule.


Est-il possible d'écrire une macro pour qu'il passe toutes les trois exemples?


Note: je présente ma propre réponse dans le cadre de la accepté de partager une astuce, mais si quelqu'un a une meilleure solution, n'hésitez pas à poster ici, il peut obtenir plus de voix que ma méthode. :)

36voto

Kip Points 37013

Il y a une solution plutôt intelligente :

Maintenant, vous avez une seule instruction de niveau de bloc, qui doit être suivie d'un point-virgule. Cela se comporte comme prévu et souhaité dans les trois exemples.

32voto

coppro Points 10692

Les Macros doit généralement être évitée; préfère les fonctions en ligne en tout temps. Un compilateur qui vaut son sel devrait être capable de l'in-lining une petite fonction comme s'il s'agissait d'une macro, et une fonction inline respecter les espaces de noms et d'autres étendues, ainsi que l'évaluation de tous les arguments d'une fois.

Si elle doit être une macro, une boucle while (déjà proposé) va travailler, ou vous pouvez essayer de 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 \
 )

L' (void)0 causes de la déclaration d'évaluer à l'une des void type, et l'utilisation de la virgule plutôt que des points-virgules lui permet d'être utilisé à l'intérieur d'un énoncé, plutôt que séparément. Je voudrais encore vous recommandons une fonction en ligne pour une multitude de raisons, dont le moindre n'étant portée et le fait qu' MACRO(a++, b++) incrémente a et b deux fois.

15voto

Steve Jessop Points 166970

Je sais que vous avez dit "ignorer l'exécution de la macro", mais les gens vont trouver cette question par la recherche basée sur le titre, donc je pense que l'examen d'autres techniques pour émuler les fonctions avec les macros sont garantie.

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)

Le résultat est le suivant:

  • Fonctionne correctement dans chacun des contextes.
  • Évalue chacun de ses arguments exactement une fois, ce qui est une garantie de la fonctionnalité d'un appel de fonction (en supposant que dans les deux cas, aucune exception dans l'un de ces expressions).
  • Agit sur tous les types, par l'utilisation de "auto" à partir de C++0x. Ce n'est pas encore la norme C++, mais il n'y a pas d'autre moyen pour obtenir le tmp, rendu nécessaire par la seule règle d'évaluation.
  • Ne nécessite pas l'appelant d'avoir importé des noms à partir de l'espace de noms std, qui la macro d'origine, mais une fonction qui ne serait pas.

Cependant, il diffère encore à partir d'une fonction en ce que:

  • Dans certains invalides utilise il peut donner un compilateur différent des erreurs ou des avertissements.
  • Il en va de mal, si X ou Y contenir les utilisations de "MACRO_tmp_1" ou "MACRO_tmp_2' à partir de l'environnement de la portée.
  • Liées à l'espace de noms std chose: une fonction qui utilise son propre lexicales en contexte pour rechercher des noms, alors qu'une macro utilise le contexte de son appel site. Il n'y a aucune façon d'écrire une macro qui se comporte comme une fonction à cet égard.
  • Il ne peut pas être utilisé comme retour de l'expression d'une fonction void, qui un vide d'expression (comme la virgule solution) peut. C'est même plus un problème lorsque le type de retour n'est pas nulle, en particulier lorsqu'il est utilisé comme une lvalue. Mais la virgule solution ne peut pas inclure l'utilisation de déclarations, parce qu'ils sont énoncés, afin de choisir l'un ou de l'utilisation de la ({ ... }) extension GNU.

9voto

TeKa Points 1368

Voici une réponse venant droit de l' libc6! Prendre un coup d'oeil à l' /usr/include/x86_64-linux-gnu/bits/byteswap.h, j'ai trouvé l'astuce que vous recherchez.

Quelques critiques de solutions précédentes:

  • De Kip solution ne permet pas l'évaluation d'une expression, qui est en fin de compte souvent nécessaire.
  • coppro la solution ne permet pas l'affectation d'une variable comme les expressions sont séparées, mais peut évaluer une expression.
  • Steve Jessop est la solution utilise le C++11 auto mot-clé, c'est bien, mais n'hésitez pas à utiliser le sait/type prévu à la place.

L'astuce est d'utiliser à la fois l' (expr,expr) construire et un {} 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 de l' register mot-clé, c'est qu'une indication pour le compilateur. L' X et Y macro paramètres sont (déjà) entouré de parenthèses et coulé à un type attendu. Cette solution fonctionne correctement avec pré - et post-incrémentation) en tant que paramètres sont évaluées qu'une seule fois.

Pour l'exemple d'une fin, même si pas demandé, j'ai ajouté de l' __x + __y; déclaration, qui est la façon de faire de l'ensemble de bloc pour être évalués en tant qu'expression précise.

Il est plus sûr d'utiliser des void(); si vous voulez vous assurer que la macro ne pas évaluer une expression, donc illégal où un rvalue est attendue.

Cependant, la solution n'est pas ISO C++ compatible que va se plaindre g++ -pedantic:

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

Afin de donner un peu de repos à l' g++, utilisez (__extension__ OLD_WHOLE_MACRO_CONTENT_HERE) , de sorte que la nouvelle définition se lit comme suit:

#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 ma solution, même un peu plus, nous allons utiliser l' __typeof__ mot-clé, comme on le voit dans MIN et MAX dans 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; \
    } \
  ))

Maintenant que le compilateur va déterminer le type approprié. C'est aussi une gcc extension.

Remarque la suppression de l' register mot-clé, comme ce serait le message d'avertissement suivant lorsqu'il est utilisé avec un type de classe:

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

2voto

Andrew Stein Points 6344

Créer un bloc à l'aide

N'ajoutez pas un ; après le moment (faux)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X