Question

Disons que pour une raison quelconque, vous devez écrire une macro: MACRO (X, Y) . (Supposons qu'il existe une bonne raison pour laquelle vous ne pouvez pas utiliser une fonction en ligne.) vous souhaitez 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 d'erreur de compilation.

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

Exemple 3: ceci ne devrait pas être compilé .

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

La façon na nelle 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 pour les trois exemples et je n’aurais pas besoin d’expliquer pourquoi.

Ignorer ce que la macro fait réellement, ce n'est pas la question.

Maintenant, je vois le plus souvent les macros écrites est de les entourer d'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;  \
}

Ceci résout l'exemple 1, car la macro est dans un bloc d'instructions. Mais l'exemple 2 est cassé parce que nous mettons un point-virgule après l'appel de 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! Enfin, l'exemple 3 compile bien, même s'il n'y a pas de point-virgule, car un bloc de code n'a pas besoin de point-virgule.

Y a-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 des moyen accepté de partager un conseil , mais si quelqu'un a une meilleure solution, n'hésitez pas à le poster ici, il se peut qu'il obtienne plus de votes que ma méthode. :)

Était-ce utile?

La solution

Les macros doivent généralement être évitées. préférez-leur des fonctions en ligne à tout moment. Tout compilateur digne de ce nom devrait pouvoir insérer une petite fonction en ligne, comme s'il s'agissait d'une macro. Une fonction en ligne respectera les espaces de noms et les autres portées, tout en évaluant tous les arguments une fois.

S'il s'agit d'une macro, une boucle while (déjà suggérée) fonctionnera ou vous pouvez essayer l'opérateur de 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 \
 )

Le (void) 0 provoque l'évaluation de l'instruction en l'un des types void , et l'utilisation de virgules plutôt que de points-virgules permet son utilisation dans une instruction, plutôt que seulement en tant que autonome. Je recommanderais quand même une fonction inline pour une foule de raisons, la moindre étant la portée et le fait que MACRO (a ++, b ++) incrémente a et b deux fois.

Autres conseils

Il existe une solution plutôt intelligente:

#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 avez maintenant une seule déclaration de niveau 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 la macro fait", mais les gens trouveront cette question en cherchant sur le titre, je pense donc que la discussion sur d'autres techniques permettant d'imiter 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 une seule fois chacun de ses arguments, ce qui est une caractéristique garantie d'un appel de fonction (dans les deux cas, aucune exception ne figure dans ces expressions).
  • Agit sur tous les types, en utilisant "auto". de C ++ 0x. Ce n’est pas encore le C ++ standard, mais il n’ya pas d’autre moyen d’obtenir les variables tmp requises par la règle d’évaluation unique.
  • N'exige pas que l'appelant ait importé les noms de namespace std, contrairement à la macro d'origine, contrairement à une fonction.

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

  • Dans certaines utilisations non valides, cela peut entraîner différentes erreurs ou avertissements du compilateur.
  • Cela se passe mal si X ou Y contient les utilisations de "MACRO_tmp_1" ou "MACRO_tmp_2" de la portée environnante.
  • En rapport avec l'élément de nom std de l'espace de nom: une fonction utilise son propre contexte lexical pour rechercher des noms, tandis qu'une macro utilise le contexte de son site d'appels. Il n’ya 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 de virgule) peut. Cela est encore plus problématique lorsque le type de retour souhaité n'est pas annulé, en particulier lorsqu'il est utilisé en tant que lvalue. Mais la solution par des virgules ne peut pas inclure l’utilisation de déclarations, car ce sont des instructions. Choisissez-en une ou utilisez l’extension ({...}) GNU.

Voici une réponse venant directement du libc6 ! En examinant /usr/include/x86_64-linux-gnu/bits/byteswap.h , j'ai trouvé le truc que vous recherchiez.

Quelques critiques des solutions précédentes:

    La solution de
  • Kip ne permet pas d’évaluer une expression , ce qui est souvent nécessaire à la fin.
  • La solution de
  • coppro ne permet pas d'affecter une variable car les expressions sont distinctes, mais peuvent être évaluées comme une expression.
  • La solution de Steve Jessop utilise le mot clé C ++ 11 auto , c'est correct, mais vous pouvez 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 entre parenthèses et convertis selon le type attendu. Cette solution fonctionne correctement avec les pré et post-incréments car les paramètres ne sont évalués qu'une seule fois.

À titre d'exemple, même si cela n'a pas été demandé, j'ai ajouté l'instruction __ x + __y; , qui permet de faire en sorte que le bloc entier soit évalué en tant qu'expression précise.

Il est préférable d'utiliser void (); si vous voulez vous assurer que la macro ne sera pas évaluée comme une expression. Par conséquent, il est illégal dans les cas où une rvalue est attendue.

Toutefois, , la solution n'est pas conforme à ISO C ++ , comme le dénoncera g ++ -pedantic :

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

Afin de laisser un peu de 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; \
    } \
  ))

Pour améliorer encore un peu plus ma solution, utilisons le mot clé __ typeof __ , comme indiqué 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 déterminera maintenant le type approprié. Ceci aussi est une extension gcc .

Notez la suppression du mot clé register , comme dans 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, ce qui peut être incroyablement utile 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 le pouvoir générateur des macros, mais vous disposez d'une portée confortable dans laquelle vous pouvez retourner ce que vous voulez (y compris void ). De plus, on évite d’évaluer plusieurs fois les paramètres de la macro.

Créer un bloc avec

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

N'ajoutez pas a; 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é, évitez 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 fonctionnalité 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 correspondre à vos attentes!

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

Si vous souhaitez adopter la pratique consistant à toujours utiliser des accolades dans vos déclarations if,

Votre macro manque simplement le dernier point-virgule:

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

Exemple 1: (compile)

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

Exemple 2: (compile)

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

Exemple 3: (ne compile pas)

do_something();
MACRO(x, y)
do_something();
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top