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
oCHOICE2
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
oCHOICE2
son largos, puede#define CHOICE1_TAG actual_contents
en su archivo de encabezado donde originalmente pretendía definirMAGIC
y luego-D
conMAGIC=CHOICE1_TAG
, porqueCHOICE1_TAG
se expandirá automáticamente aactual_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)