Pregunta

Digamos que, por algún motivo, necesita escribir una macro: MACRO (X, Y) . (Supongamos que hay una buena razón por la que no puede usar una función en línea). Desea que esta macro emule una llamada a una función sin valor de retorno.


Ejemplo 1: Esto debería funcionar como se esperaba.

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

Ejemplo 2: Esto no debería generar un error de compilación.

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

Ejemplo 3: Esto no debería no compilar.

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

La forma ingenua de escribir la macro es así:

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

Esta es una solución muy mala que falla en los tres ejemplos, y no debería necesitar explicar por qué.

Ignora lo que realmente hace la macro, ese no es el punto.


Ahora, la forma en que más a menudo veo macros escritas es encerrándolas entre llaves, como esto:

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

Esto resuelve el ejemplo 1, porque la macro está en un bloque de declaración. Pero el ejemplo 2 está roto porque ponemos un punto y coma después de la llamada a la macro. ¡Esto hace que el compilador piense que el punto y coma es una declaración por sí misma, lo que significa que la instrucción else no se corresponde con ninguna instrucción if! Y por último, el ejemplo 3 compila OK, aunque no haya un punto y coma, porque un bloque de código no necesita un punto y coma.


¿Hay una manera de escribir una macro para que pase los tres ejemplos?


Nota: Estoy enviando mi propia respuesta como parte de la forma aceptada de compartir un consejo , pero si alguien tiene una solución mejor, no dude en publicarlo aquí, puede obtener más votos que mi método. :)

¿Fue útil?

Solución

Las macros generalmente deben evitarse; Prefiero las funciones en línea a ellos en todo momento. Cualquier compilador que valga la pena debe ser capaz de alinear una función pequeña como si fuera una macro, y una función en línea respetará los espacios de nombres y otros ámbitos, así como la evaluación de todos los argumentos una vez.

Si debe ser una macro, funcionará un bucle while (ya sugerido), o puede probar el operador de coma:

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

El (void) 0 hace que la declaración se evalúe a uno de los tipos void , y el uso de comas en lugar de puntos y comas permite que se use dentro de una declaración, en lugar de solo como independiente. Seguiría recomendando una función en línea por una serie de razones, la menor de las cuales es el alcance y el hecho de que MACRO (a ++, b ++) incrementará a y b dos veces.

Otros consejos

Hay una solución bastante inteligente:

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

Ahora tiene una sola instrucción de nivel de bloque, que debe ir seguida de un punto y coma. Esto se comporta como se espera y se desea en los tres ejemplos.

Sé que dijiste "ignorar lo que hace la macro", pero la gente encontrará esta pregunta buscando en el título, por lo que creo que la discusión de otras técnicas para emular funciones con macros está justificada.

Lo más cercano que conozco es:

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

Esto hace lo siguiente:

  • Funciona correctamente en cada uno de los contextos establecidos.
  • Evalúa cada uno de sus argumentos exactamente una vez, lo cual es una característica garantizada de una llamada de función (asumiendo que en ambos casos no hay excepciones en ninguna de esas expresiones).
  • Actúa en cualquier tipo, mediante el uso de " auto " de C ++ 0x. Esto aún no es C ++ estándar, pero no hay otra manera de obtener las variables tmp requeridas por la regla de evaluación única.
  • No requiere que la persona que llama tenga nombres importados del espacio de nombres estándar, lo que hace la macro original, pero una función no.

Sin embargo, todavía difiere de una función en que:

  • En algunos usos no válidos puede dar diferentes errores o advertencias del compilador.
  • Saldrá mal si X o Y contienen usos de 'MACRO_tmp_1' o 'MACRO_tmp_2' del ámbito circundante.
  • En relación con la cosa estándar del espacio de nombres: una función usa su propio contexto léxico para buscar nombres, mientras que una macro usa el contexto de su sitio de llamadas. No hay forma de escribir una macro que se comporte como una función a este respecto.
  • No se puede utilizar como la expresión de retorno de una función void, como puede hacerlo una expresión void (como la solución de coma). Este es un problema aún mayor cuando el tipo de retorno deseado no es nulo, especialmente cuando se utiliza como un valor l. Pero la solución de coma no puede incluir el uso de declaraciones, porque son declaraciones, así que elige una o usa la extensión GNU ({...}).

¡Aquí hay una respuesta que viene directamente del libc6 ! Echando un vistazo a /usr/include/x86_64-linux-gnu/bits/byteswap.h , encontré el truco que estaba buscando.

Algunas críticas de soluciones anteriores:

  • La solución de Kip no permite que se evalúe a una expresión , que al final es a menudo necesaria.
  • La solución de
  • coppro no permite asignar una variable ya que las expresiones son independientes, pero se puede evaluar a una expresión.
  • La solución de
  • Steve Jessop usa la palabra clave C ++ 11 auto , está bien, pero puede utilizar el tipo conocido / esperado en su lugar.

El truco es usar el constructo (expr, expr) y el alcance {} :

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

Tenga en cuenta el uso de la palabra clave register , es solo una sugerencia para el compilador. Los parámetros de macro X y Y están (ya) rodeados entre paréntesis y convertidos a un tipo esperado. Esta solución funciona correctamente con el incremento previo y posterior, ya que los parámetros se evalúan solo una vez.

Para el propósito de ejemplo, aunque no se solicitó, agregué la declaración __x + __y; , que es la manera de hacer que todo el bloque se evalúe como esa expresión precisa.

Es más seguro usar void (); si desea asegurarse de que la macro no se evalúe como una expresión, por lo que es ilegal donde se espera un rvalue .

Sin embargo , la solución no es compatible con ISO C ++ como se quejará g ++ -pedantic :

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

Para darle un poco de descanso a g ++ , use (__extension__ OLD_WHOLE_MACRO_CONTENT_HERE) para que la nueva definición lea:

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

Para mejorar mi solución un poco más, usemos la palabra clave __typeof__ , como se ve en MIN y 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; \
    } \
  ))

Ahora el compilador determinará el tipo apropiado. Esto también es una extensión de gcc .

Observe la eliminación de la palabra clave register , como lo haría con la siguiente advertencia cuando se use con un tipo de clase:

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

C ++ 11 nos trajo lambdas, lo que puede ser increíblemente útil en esta situación:

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

Mantiene el poder generativo de las macros, pero tiene un ámbito cómodo desde el que puede devolver lo que quiera (incluido void ). Además, se evita el problema de evaluar los parámetros de la macro varias veces.

Crea un bloque usando

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

No agregue un; después de un rato (falso)

Su respuesta sufre del problema de evaluación múltiple, por lo que (por ejemplo)

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

hará algo inesperado y probablemente no deseado.

Como han mencionado otros, debes evitar las macros siempre que sea posible. Son peligrosos en presencia de efectos secundarios si los argumentos de la macro se evalúan más de una vez. Si conoce el tipo de argumentos (o puede usar la función auto de C ++ 0x), podría usar temporales para imponer una evaluación única.

Otro problema: ¡el orden en el que se realizan las múltiples evaluaciones puede que no sea el esperado!

Considera este código:

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

Y se imprime como se compila y ejecuta en mi máquina:

X=100, Y=10000, X+Y=110
X=10, Y=100, X+Y=110

Si estás dispuesto a adoptar la práctica de usar siempre llaves en tus declaraciones if,

A tu macro simplemente le faltará el último punto y coma:

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

Ejemplo 1: (compilaciones)

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

Ejemplo 2: (compilaciones)

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

Ejemplo 3: (no compila)

do_something();
MACRO(x, y)
do_something();
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top