visual studio proyecto dev crear como code c++ boost macros c-preprocessor boost-preprocessor

c++ - studio - ¿Se puede implementar BOOST_PP_DEFINED?



visual studio c++ (3)

¿Es posible escribir una macro de preprocesador de función similar a C que devuelve 1 si su argumento está definido y 0 caso contrario? Vamos a llamarlo BOOST_PP_DEFINED por analogía con las otras macros del preprocesador boost, que podemos suponer que también están en juego:

#define BOOST_PP_DEFINED(VAR) ??? #define XXX BOOST_PP_DEFINED(XXX) // expands to 1 #undef XXX BOOST_PP_DEFINED(XXX) // expands to 0

Estoy esperando usar el resultado de BOOST_PP_DEFINED con BOOST_PP_IIF :

#define MAGIC(ARG) BOOST_PP_IIF(BOOST_PP_DEFINED(ARG), CHOICE1, CHOICE2)

En otras palabras, quiero que la expansión de MAGIC(ARG) varíe dependiendo de si ARG está definido o no en el momento en que se expande MAGIC :

#define FOO MAGIC(FOO) // expands to CHOICE1 (or the expansion of CHOICE1) #undef FOO MAGIC(FOO) // expands to CHOICE2 (or the expansion of CHOICE2)

También me pareció interesante (y algo sorprendente) que lo siguiente no funciona:

#define MAGIC(ARG) BOOST_PP_IIF(defined(arg), CHOICE1, CHOICE2)

Porque aparentemente defined solo es válido en el preprocesador cuando se usa como parte de una expresión #if .

De alguna manera sospecho que el hecho de que el preprocesador boost aún no ofrezca BOOST_PP_DEFINED es una evidencia de su imposibilidad, pero no está de más preguntarlo. O, me estoy perdiendo algo realmente obvio sobre cómo lograr esto.

EDITAR : Para agregar un poco de motivación, aquí es por qué quiero esto. La forma tradicional de hacer macros "API" para controlar la importación / exportación es declarar un nuevo conjunto de macros para cada biblioteca, lo que significa un nuevo encabezado, etc. Entonces, si tenemos la class Base en libbase y la class Derived en libderived , entonces tenga algo como lo siguiente:

// base_config.hpp #if LIBBASE_COMPILING #define LIBBASE_API __declspec(dllexport) #else #define LIBBASE_API __declspec(dllimport) // base.hpp #include "base_config.hpp" class LIBBASE_API base { public: base(); }; // base.cpp #include "base.hpp" base::base() = default; // derived_config.hpp #if LIBDERIVED_COMPILING #define LIBDERIVED_API __declspec(dllexport) #else #define LIBDERIVED_API __declspec(dllimport) // derived.hpp #include "derived_config.hpp" #include "base.hpp" class LIBDERIVED_API derived : public base { public: derived(); }; // derived.cpp #include "derived.hpp" derived::derived() = default;

Ahora, obviamente, cada uno del encabezado _config.hpp realmente sería mucho más complejo, definiendo varias macros. Probablemente podríamos sacar algunos de los elementos en común en un archivo genérico config_support.hpp , pero no todos. Entonces, en un esfuerzo por simplificar este lío, me pregunté si sería posible hacer esto genérico, para que se pudiera usar un conjunto de macros, pero eso se expandiría de manera diferente en _COMPILING macros _COMPILING que estaban en juego:

// config.hpp #define EXPORT __declspec(dllexport) #define IMPORT __declspec(dllimport) #define API_IMPL2(COND) BOOST_PP_IIF(COND, EXPORT, IMPORT)() #define API_IMPL(ARG) API_IMPL2(BOOST_PP_DEFINED(ARG)) #define API(LIB) API_IMPL(LIB ## _COMPILING) // base.hpp #include "config.hpp" class API(LIBBASE) base { public: base(); }; // base.cpp #include "base.hpp" base::base() = default; // derived.hpp #include "config.hpp" #include "base.hpp" class API(LIBDERIVED) derived : public base { public: derived(); }; // derived.cpp #include "derived.hpp" derived::derived() = default;

En otras palabras, al compilar base.cpp , API(LIBBASE) se expandiría a __declspec(dllexport) porque se definió LIBBASE_COMPILING en la línea de comandos, pero al compilar API(LIBBASE) se expandiría a __declspec(dllimport) porque LIBBASE_COMPILING era no definido en la línea de comando, pero API(LIBDERIVED) ahora se expandiría a __declspec(dllexport) ya que LIBDERIVED_COMPILING sería. Pero para que esto funcione, es fundamental que la macro API expanda contextualmente.


Como tiene la intención de utilizar FOO como conmutador de nivel de archivo que usted controla, le sugiero que use una solución más simple. La solución sugerida es más fácil de leer, menos sorprendente, no requiere magia sucia.

En lugar de #define MAGIC(ARG) BOOST_PP_IIF(BOOST_PP_DEFINED(ARG), CHOICE1, CHOICE2) simplemente -D con MAGIC=CHOICE1 o MAGIC=CHOICE2 por archivo.

  • No tienes que hacerlo para todos los archivos. El compilador le dirá cuándo utilizó MAGIC en un archivo, pero no tomó ninguna decisión.
  • Si CHOICE2 o CHOICE2 es un valor predeterminado importante que no desea especificar, puede usar -D para establecer el valor predeterminado para todos los archivos y -U + -D para cambiar su decisión por archivo.
  • Si CHOICE2 o CHOICE2 son largos, puede #define CHOICE1_TAG actual_contents en su archivo de encabezado donde originalmente pretendía definir MAGIC y luego -D con MAGIC=CHOICE1_TAG , porque CHOICE1_TAG se expandirá automáticamente a actual_contents .

No se trata de una verificación puramente definida, pero podemos obtener todo el camino para buscar un nombre de token en particular.

Anotando una primera solución de principios basada en Cloak de Paul Fultz II:

Primero, proporcione la capacidad de elegir condicionalmente el texto basado en la expansión de macros a 0 o 1

#define IIF(bit) PRIMITIVE_CAT(IIF_, bit) #define IIF_0(t, f) f #define IIF_1(t, f) t

Concatenación básica

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__) #define PRIMITIVE_CAT(a, ...) a##__VA_ARGS__

Operadores lógicos (cumplido y)

#define COMPL(b) PRIMITIVE_CAT(COMPL_, b) #define COMPL_0 1 #define COMPL_1 0 #define BITAND(x) PRIMITIVE_CAT(BITAND_, x) #define BITAND_0(y) 0 #define BITAND_1(y) y

Un método para ver si un token es o no parens "()"

#define CHECK_N(x, n, ...) n #define CHECK(...) CHECK_N(__VA_ARGS__, 0, ) #define PROBE(x) x, 1, #define IS_PAREN(x) CHECK(IS_PAREN_PROBE x) #define IS_PAREN_PROBE(...) PROBE(~)

Nota: IS_PAREN funciona porque "IS_PAREN_PROBE X" se convierte en una arg en CHECK (), donde "IS_PAREN_PROBE ()" se convierte en PROBE (~) que se convierte en ~, 1. En ese punto podemos recoger el 1 de CHECK

Otra utilidad para comer algunos argumentos de macro según sea necesario

#define EAT(...)

Aquí, aprovechamos la pintura azul (lo que impide que las macros ingenuamente recursivas) verifiquen si dos tokens son iguales o no. Si son esto colapsa a (). De lo contrario, no, que podemos detectar a través de IS_PAREN.

Esto se basa en COMPARE_XXX macros de identidad existentes para cualquier símbolo dado

#define PRIMITIVE_COMPARE(x, y) IS_PAREN(COMPARE_##x(COMPARE_##y)(()))

Agregamos un rasgo IS_COMPARABLE para ese ayudante

#define IS_COMPARABLE(x) IS_PAREN(CAT(COMPARE_, x)(()))

Trabajamos hacia atrás a EQUAL comprobando si ambas args son comparables, luego convirtiendo a primitive_compare si lo son. Si no, no somos iguales y comemos los siguientes argumentos.

#define NOT_EQUAL(x, y) / IIF(BITAND(IS_COMPARABLE(x))(IS_COMPARABLE(y))) / (PRIMITIVE_COMPARE, 1 EAT)(x, y)

EQUAL es el cumplido

#define EQUAL(x, y) COMPL(NOT_EQUAL(x, y))

Y finalmente, la macro que realmente queremos.

Primero habilitamos la comparación para "BUILDING_LIB"

#define COMPARE_BUILDING_LIB(x) x

Entonces nuestra macro decisiva real, que es un número entero si se trata de si un símbolo se resuelve como "BUILDING_LIB"

#define YES_IF_BUILDING_LIB(name) IIF(EQUAL(name, BUILDING_LIB))("yes", "no") #include <iostream> #define FOO BUILDING_LIB int main(int, char**) { std::cout << YES_IF_BUILDING_LIB(FOO) << "/n"; std::cout << YES_IF_BUILDING_LIB(BAR) << "/n"; }

Qué salidas:

yes no

Vea su gran entrada de blog (de la que he descifrado): C Trucos, sugerencias y modismos del preprocesador


Parece que podría usar BOOST_VMD_IS_EMPTY para implementar el comportamiento requerido. Esta macro devuelve 1 si su entrada está vacía o 0 si su entrada no está vacía.

Truco basado en la observación de que cuando XXX está definido por #define XXX , la lista de parámetros vacía pasa a BOOST_VMD_IS_EMPTY(XXX) durante la expansión.

Ejemplo de implementación de MAGIC macro:

#ifndef BOOST_PP_VARIADICS #define BOOST_PP_VARIADICS #endif #include <boost/vmd/is_empty.hpp> #include <boost/preprocessor/control/iif.hpp> #define MAGIC(XXX) BOOST_PP_IIF(BOOST_VMD_IS_EMPTY(XXX), 3, 4) #define XXX int x = MAGIC(XXX); #undef XXX int p = MAGIC(XXX);

Para Boost 1.62 y VS2015, la salida del preprocesador será:

int x = 3; int p = 4;

Este enfoque tiene varios defectos, por ejemplo, no funciona si XXX define con #define XXX 1 . BOOST_VMD_IS_EMPTY tiene limitaciones .

EDITAR:

Aquí está la implementación de las macros API requeridas basadas en BOOST_VMD_IS_EMPTY :

// config.hpp #ifndef BOOST_PP_VARIADICS #define BOOST_PP_VARIADICS #endif #include <boost/vmd/is_empty.hpp> #include <boost/preprocessor/control/iif.hpp> #define EXPORT __declspec(dllexport) #define IMPORT __declspec(dllimport) #define API_IMPL2(COND) BOOST_PP_IIF(COND, EXPORT, IMPORT) #define API_IMPL(ARG) API_IMPL2(BOOST_VMD_IS_EMPTY(ARG)) #define API(LIB) API_IMPL(LIB ## _COMPILING)

Veamos para qué producirá el preprocesador:

// base.hpp #include "config.hpp" class API(LIBBASE) base { public: base(); };

Cuando se definió LIBBASE_COMPILING , salida de GCC:

class __attribute__((dllexport)) Base { public: Base(); };

Cuando LIBBASE_COMPILING no está definido, salida de GCC:

class __attribute__((dllimport)) Base { public: Base(); };

Probado con VS2015 y GCC 5.4 (Cygwin)

EDIT 2: como @acm se menciona cuando el parámetro definido con -DFOO es igual que -DFOO=1 o #define FOO 1 . En este caso, el enfoque basado en BOOST_VMD_IS_EMPTY no funciona. Para superarlo, puede usar BOOST_VMD_IS_NUMBER (thnx a @jv_ para la idea). Implementación:

#ifndef BOOST_PP_VARIADICS #define BOOST_PP_VARIADICS #endif #include <boost/vmd/is_number.hpp> #include <boost/preprocessor/control/iif.hpp> #define EXPORT __declspec(dllexport) #define IMPORT __declspec(dllimport) #define API_IMPL2(COND) BOOST_PP_IIF(COND, EXPORT, IMPORT) #define API_IMPL(ARG) API_IMPL2(BOOST_VMD_IS_NUMBER(ARG)) #define API(LIB) API_IMPL(LIB ## _COMPILING)