web-dev-qa-db-fra.com

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

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 .


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: Cela devrait pas compiler.

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. :)

46
Kip

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.

40
coppro

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.

43
Kip

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:

  • Fonctionne correctement dans chacun des contextes indiqués.
  • Évalue chacun de ses arguments exactement une fois, ce qui est une caractéristique garantie d'un appel de fonction (en supposant dans les deux cas aucune exception dans aucune de ces expressions).
  • Agit sur tous les types, en utilisant "auto" de C++ 0x. Ce n'est pas encore du C++ standard, mais il n'y a pas d'autre moyen d'obtenir les variables tmp rendues nécessaires par la règle d'évaluation unique.
  • N'exige pas que l'appelant ait importé des noms de l'espace de noms std, ce que fait la macro d'origine, mais pas une fonction.

Cependant, il diffère toujours d'une fonction en ce que:

  • Dans certaines utilisations non valides, il peut donner différentes erreurs ou avertissements du compilateur.
  • Cela va mal si X ou Y contiennent des utilisations de 'MACRO_tmp_1' ou 'MACRO_tmp_2' de la portée environnante.
  • Lié à la chose std d'espace de noms: une fonction utilise son propre contexte lexical pour rechercher des noms, tandis qu'une macro utilise le contexte de son site d'appel. Il n'y a aucun moyen d'écrire une macro qui se comporte comme une fonction à cet égard.
  • Il ne peut pas être utilisé comme expression de retour d'une fonction void, ce qu'une expression void (telle que la solution virgule) peut. C'est encore plus un problème lorsque le type de retour souhaité n'est pas nul, en particulier lorsqu'il est utilisé comme valeur l. Mais la solution virgule ne peut pas inclure l'utilisation de déclarations, car ce sont des instructions, alors choisissez-en une ou utilisez l'extension ({...}) GNU.
17
Steve Jessop

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:

  • La solution de Kip ne permet pas d'évaluer une expression, ce qui est finalement souvent nécessaire.
  • la solution de coppro ne permet pas d'attribuer une variable car les expressions sont séparées, mais peuvent évaluer une expression.
  • La solution de Steve Jessop utilise le mot-clé C++ 11 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]
12
ofavre

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é.

6
Quentin

Créer un bloc à l'aide

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

N'ajoutez pas de; après le temps (faux)

4
Andrew Stein

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.

2
Mike F

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 
0
jwfearn