c++ - que - Imprime valores de macro sin saber la cantidad de macros
micronutrientes (3)
Tengo un código que incluye un archivo generado (no sé de antemano su contenido), solo hay una convención en la que mis usuarios y yo acordamos cómo crear este archivo para poder usarlo. Este archivo parece
#define MACRO0 "A"
#define MACRO1 "B"
#define MACRO2 "C"
...
Quiero imprimir todos los valores de macros. Mi código actual parece
#ifdef MACRO0
std::cout << "MACRO0 " << MACRO0 << std::endl;
#endif
#ifdef MACRO1
std::cout << "MACRO1 " << MACRO1 << std::endl;
#endif
#ifdef MACRO2
std::cout << "MACRO2 " << MACRO2 << std::endl;
#endif
Mi pregunta es, cómo iterar sobre las macros en el archivo generado para no tener que duplicar tanto mi código
En primer lugar, sabemos que podemos contar con Boost.Preprocessor para nuestras necesidades de bucle. Sin embargo, el código generado debe funcionar por sí solo. Desafortunadamente, #ifdef
no puede funcionar como resultado de la expansión de macros, por lo que no hay forma de generar el código en su pregunta. ¿Estamos tostados?
¡Aún no! Podemos aprovechar el hecho de que todas sus macros son inexistentes o una cadena literal. Considera lo siguiente:
using StrPtr = char const *;
StrPtr probe(StrPtr(MACRO1));
Estamos aprovechando de nuestro viejo amigo el análisis más molesto aquí. La segunda línea se puede interpretar de dos maneras dependiendo de si MACRO1
está definido. Sin ella, es equivalente a:
char const *probe(char const *MACRO1);
... que es una declaración de función donde MACRO1
es el nombre del parámetro. Pero, cuando MACRO1
se define como "B"
, se convierte en equivalente a:
char const *probe = (char const *) "B";
... que es una variable inicializada para apuntar a "B"
. Luego podemos activar el tipo de lo que acabamos de producir para ver si se produjo una sustitución:
if(!std::is_function<decltype(probe)>::value)
std::cout << "MACRO1 " << probe << ''/n'';
Podríamos usar if constexpr
aquí, pero std::cout
puede generar un puntero de función (lo convierte en bool
), por lo que la rama muerta es válida y el compilador es lo suficientemente inteligente como para optimizarla por completo.
Finalmente, volvemos a Boost.Preprocessor para generar todas esas cosas para nosotros:
#define PRINT_IF_DEFINED(z, n, data) /
{ /
StrPtr probe(StrPtr(BOOST_PP_CAT(MACRO, n))); /
if(!std::is_function<decltype(probe)>::value) /
std::cout << "MACRO" BOOST_PP_STRINGIZE(n) " " << probe << ''/n''; /
}
#define PRINT_MACROS(num) /
do { /
using StrPtr = char const *; /
BOOST_PP_REPEAT(num, PRINT_IF_DEFINED, ~) /
} while(false)
... voilà!
Nota: el fragmento de código de Coliru incluye desactivadores de advertencia para GCC y Clang, que advierten contra nuestro pobre amigo el análisis más molesto :(
Esta respuesta está escrita teniendo en cuenta una pregunta de seguimiento .
C ++ tiene soporte para la programación genérica que a menudo elimina la necesidad de preprocesador. En este caso, sería mejor hacer un conjunto de rasgos de tipo que declaren las propiedades de los parámetros que deben manejarse, reduciendo la función del preprocesador a la compilación condicional (o eliminándola por completo si se supone que este código se genera cada vez):
enum class
t_Param
{
begin, a = begin, b, c, d, e, z, end
};
template<t_Param param, typename TEnabled = void> class
t_ParamIsEnabled final: public ::std::true_type
{};
template<t_Param param> class
t_ParamIsEnabled
<
param
, typename ::std::enable_if
<
(t_Param::end == param)
#ifndef A1
|| (t_Param::a == param)
#endif
#ifndef B2
|| (t_Param::b == param)
#endif
#ifndef C3
|| (t_Param::c == param)
#endif
#ifndef D4
|| (t_Param::d == param)
#endif
#ifndef E5
|| (t_Param::e == param)
#endif
>::type
> final: public ::std::false_type
{};
template<t_Param param> class
t_ParamTrait;
template<> class
t_ParamTrait<t_Param::a> final
{
public: static constexpr auto const & num{"1"};
public: static constexpr auto const & val{"A"};
};
template<> class
t_ParamTrait<t_Param::b> final
{
public: static constexpr auto const & num{"2"};
public: static constexpr auto const & val{"B"};
};
template<> class
t_ParamTrait<t_Param::c> final
{
public: static constexpr auto const & num{"3"};
public: static constexpr auto const & val{"C"};
};
template<> class
t_ParamTrait<t_Param::d> final
{
public: static constexpr auto const & num{"4"};
public: static constexpr auto const & val{"D"};
};
template<> class
t_ParamTrait<t_Param::e> final
{
public: static constexpr auto const & num{"5"};
public: static constexpr auto const & val{"E"};
};
template<> class
t_ParamTrait<t_Param::z> final
{
public: static constexpr auto const & num{"26"};
public: static constexpr auto const & val{"ZZ"};
};
Esto le permitirá iterar sobre los parámetros y consultar sus propiedades usando un código genérico:
template<t_Param param> typename ::std::enable_if<t_ParamIsEnabled<param>::value>::type
Echo(void)
{
::std::cout << t_ParamTrait<param>::val << ":" << t_ParamTrait<param>::num << ::std::endl;
}
template<t_Param param> typename ::std::enable_if<!t_ParamIsEnabled<param>::value>::type
Echo(void)
{
// Do nothing
}
template<int param_index = 0> void
Echo_All(void)
{
Echo<static_cast<t_Param>(param_index)>();
Echo_All<param_index + 1>();
}
template<> void
Echo_All<static_cast<int>(t_Param::end)>(void)
{
// Do nothing.
}
int main()
{
Echo_All();
return 0;
}
Me encontré con el mismo tipo de necesidad hace mucho tiempo.
Mi solución fue usar el preprocesador, pero no obtener la respuesta "dentro del código".
Por ejemplo, clang++ -dM -E test.cpp
generará todas las macros. (En ese momento, usé gcc
, pero la misma técnica funciona para GCC, CLang y CL.EXE de Visual Studio ... los modificadores del compilador pueden variar).
Ahh, drat, que también incluye todas las macros predefinidas.
Así que produciría un archivo de "lista negra" de las macros predefinidas que no me importaban, y luego lo usaría para filtrar esos resultados (usando grep -v
).
El otro problema que encontré fue que a veces alguien #undef IMPORTANT_MACRO
que luego se perdería en el volcado. Para esas situaciones infrecuentes ... y luego comenzaron los asesinatos .