31

I have code that includes a generated file (I don't know in advance its content), there is just a convention upon which me and my users agreed on how to create this file so I can use it. This file looks like

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

I want to print all macros values. My current code looks like

#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

My question is, how to iterate over the macros in the generated file so I don't need to duplicate my code so much

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
e271p314
  • 3,841
  • 7
  • 36
  • 61
  • Write a short preprocessing script in your language of choice to generate your code block. Run it ahead of compiling. It's the only sane thing to do IMO, if you are backed into a corner by that file you and your users have. – StoryTeller - Unslander Monica Nov 23 '17 at 10:18
  • The real code is much more complicated. I prefer to have the code written explicitly and not generated – e271p314 Nov 23 '17 at 10:20
  • 1
    If the "real code" is much more complicated, who's to say anything suggested here is even going to apply at all? – StoryTeller - Unslander Monica Nov 23 '17 at 10:21
  • 3
    If the generation of the file containing `MACROn` is under your control, why not further enhance it to solve this problem for you? – GManNickG Nov 23 '17 at 10:21
  • Use a non-standard preprocessor which have extra directives like loops? – Some programmer dude Nov 23 '17 at 10:21
  • Why have you agreed to a file format for you to use if you cannot use that file format? –  Nov 23 '17 at 10:47
  • Historically, when I started to develop the system, I was willing to accept code duplication for the system to work as expected, as time went by the code began to be more complicated and I want to reduce code duplication to make more maintainable. I don't want to change the file format, as I have obligation to my users not to disturb them more than learning the convention we agreed upon – e271p314 Nov 23 '17 at 10:53

3 Answers3

55

First of all, we know we can count on Boost.Preprocessor for our looping needs. However, the generated code must work on its own. Unfortunately, #ifdef cannot work as a result of macro expansion, so there's no way to generate the code in your question. Are we toasted?

Not yet! We can take advantage of the fact that your macros are all either nonexistent or a string literal. Consider the following:

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

We're taking advantage of our old friend the most vexing parse here. The second line can be interpreted in two ways depending on whether MACRO1 is defined. Without it, it is equivalent to:

char const *probe(char const *MACRO1);

... which is a function declaration where MACRO1 is the name of the parameter. But, when MACRO1 is defined to be "B", it becomes equivalent to:

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

... which is a variable initialized to point at "B". We can then switch on the type of what we just produced to see if a substitution occured:

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

We could make use of if constexpr here, but std::cout can output a function pointer (it converts it to bool) so the dead branch is valid, and the compiler is clever enough to completely optimize it out.

Finally, we come back to Boost.Preprocessor to generate all that stuff for us:

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

See it live on Coliru

Note: the Coliru snippet includes warning disablers for GCC and Clang, which warn against our poor pal the most vexing parse :(

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • 21
    You leveraged the most vexing parse... I feel I should bow and chant my unworthiness. – StoryTeller - Unslander Monica Nov 23 '17 at 11:06
  • 15
    This is legendary – M.M Nov 23 '17 at 11:06
  • Thank you very much for your wise suggestion – e271p314 Nov 23 '17 at 12:24
  • "Unfortunately, #ifdef cannot work as a result of macro expansion, so there's no way to generate the code in your question." For _completion_, it's worth mention that you don't need `#ifdef` to do conditionals in macros. You could use either something like boost's `IF` or `IIF`, or pattern matching. This _particular_ use case, however, defeats both approaches... `IF`/`IIF` require numeric-like macros to test, and C preprocessor pattern matching works by pasting/evaluating... but you can't paste to a string literal. – H Walters Nov 23 '17 at 15:21
  • 2
    _"We're taking advantage of our old friend the most vexing parse here."_ This is pure genius! – YSC Nov 29 '17 at 10:37
  • 1
    A similar approach avoiding vexing parse could use string concatenation to generate empty strings for missing macros, which can quite easily be tested for, assuming none of your defined macros can be empty: `MACRO1 ""` ==> `"A"` `MACRO401 ""` ==> `""` – Gem Taylor Nov 30 '17 at 17:57
  • I'm also wondering if there is a solution using custom literals? `MACRO1##_cl` ==> `"A"_cl` is a custom literal, while `MACRO401##_cl` ==> `_cl` is a valid variable name? – Gem Taylor Nov 30 '17 at 18:02
  • @GemTaylor but an "undefined macro" is just the identifier that is kept, not an empty token, so to the compiler that'd become `MACROX ""` and `MACROX_cl`, the first being a syntax error and the second needing further processing still. – Quentin Nov 30 '17 at 18:10
  • Wow, thank you so much @StoryTeller! I didn't expect this trick to become such a hit ^^ – Quentin Dec 03 '17 at 13:22
  • 1
    @Quentin - I wasn't joking when I originally commented :) This is pure awesome! – StoryTeller - Unslander Monica Dec 03 '17 at 13:27
3

I ran into the same kind of need a long time ago.

My solution was to use the preprocessor, but not to get the answer "within the code".

For example, clang++ -dM -E test.cpp will output all the macros. (At the time, I used gcc, but the same technique works for GCC, CLang, and Visual Studio's CL.EXE ... the compiler switches may vary.)

Ahh, drat, that also includes all the predefined macros.

So I would produce a "blacklist" file of the predefined macros that I did not care about, and then use that to filter out those results (using grep -v).

The other problem I ran into was that sometimes someone would #undef IMPORTANT_MACRO which would then get missed in the dump. For those infrequent situations... and then the murders began.

Eljay
  • 4,648
  • 3
  • 16
  • 27
0

This answer is written taking a follow-up question into account.

C++ has support for generic programming that often eliminates need for preprocessor. In this case it would be better to make a set of type traits declaring properties of parameters that need to be handled reducing role of preprocessor to conditional compilation (or eliminating it completely if this code is supposed to be generated every time):

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

This will allow you to iterate over parameters and query their properties using generic code:

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

online compiler

user7860670
  • 35,849
  • 4
  • 58
  • 84