web-dev-qa-db-fra.com

Imprimer des valeurs de macro sans connaître la quantité de macros

J'ai un code qui inclut un fichier généré (je ne connais pas à l'avance son contenu), il existe simplement une convention sur laquelle moi et mes utilisateurs nous sommes mis d'accord sur la manière de créer ce fichier afin que je puisse l'utiliser. Ce fichier ressemble à

#define MACRO0 "A"
#define MACRO1 "B"
#define MACRO2 "C"
...

Je veux imprimer toutes les valeurs de macros. Mon code actuel ressemble à

#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

Ma question est, comment parcourir les macros dans le fichier généré afin que je n'ai pas besoin de dupliquer mon code tellement

30
e271p314

Tout d'abord, nous savons que nous pouvons compter sur Boost.Preprocessor pour nos besoins en boucle. Cependant, le code généré doit fonctionner seul. Malheureusement, #ifdef ne peut pas fonctionner à la suite du développement d'une macro. Il est donc impossible de générer le code dans votre question. Sommes-nous grillés?

Pas encore! Nous pouvons tirer parti du fait que vos macros sont toutes inexistantes ou un littéral de chaîne. Considérer ce qui suit:

using StrPtr = char const *;
StrPtr probe(StrPtr(MACRO1));

Nous profitons de notre vieil ami l'analyse la plus frustrante ici. La deuxième ligne peut être interprétée de deux manières selon que MACRO1 est défini ou non. Sans cela, cela équivaut à:

char const *probe(char const *MACRO1);

... qui est une déclaration de fonction où MACRO1 est le nom du paramètre. Mais, lorsque MACRO1 est défini comme étant "B", il devient équivalent à:

char const *probe = (char const *) "B";

... qui est une variable initialisée pour pointer sur "B". Nous pouvons ensuite activer le type de ce que nous venons de produire pour voir si une substitution s'est produite:

if(!std::is_function<decltype(probe)>::value)
    std::cout << "MACRO1 " << probe << '\n';

Nous pourrions utiliser ici if constexpr, mais std::cout peut générer un pointeur de fonction (le convertit en bool) afin que la branche morte soit valide et que le compilateur soit suffisamment intelligent pour l'optimiser complètement.

Enfin, nous revenons à Boost.Preprocessor pour générer tout ce matériel pour nous:

#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à!

Voir en direct sur Coliru

Remarque: l'extrait de Coliru inclut des désactivateurs d'avertissement pour GCC et Clang, qui mettent en garde contre notre pauvre ami l'analyse la plus frustrante :(

50
Quentin

J'ai eu le même genre de besoin il y a longtemps.

Ma solution consistait à utiliser le pré-processeur, mais pas à obtenir la réponse "dans le code".

Par exemple, clang++ -dM -E test.cpp générera toutes les macros. (À l'époque, j'ai utilisé gcc, mais la même technique fonctionne pour GCC, CLang et CL.EXE de Visual Studio ... les commutateurs du compilateur peuvent varier.)

Ahh, drat, cela inclut également toutes les macros prédéfinies.

Je produisais donc un fichier "liste noire" des macros prédéfinies dont je ne me souciais pas, puis je l'utilisais pour filtrer ces résultats (en utilisant grep -v).

L’autre problème que j’ai rencontré est que parfois quelqu'un #undef IMPORTANT_MACRO était oublié dans la décharge. Pour ces situations peu fréquentes ... et ensuite les meurtres ont commencé .

3
Eljay

Cette réponse est écrite en tenant compte d'une question de suivi .

C++ prend en charge la programmation générique, ce qui élimine souvent le besoin de préprocesseur. Dans ce cas, il serait préférable de créer un ensemble de caractères de type déclarant les propriétés des paramètres devant être gérés, réduisant le rôle du pré-processeur à la compilation conditionnelle (ou l'éliminant complètement si ce code est censé être généré à chaque fois):

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

Cela vous permettra d'itérer sur les paramètres et d'interroger leurs propriétés à l'aide d'un code générique:

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

compilateur en ligne

0
VTT