Search code examples
c++macrosc-preprocessorboost-preprocessor

macro which defines new macros with an added prefix


We have a profiling framework which can be enabled and disabled at compile time.

All the various calls to the framework are done through macros, eg:

PROFILE_START(msg)
PROFILE_END(msg)

The macros then resolve to the actual profiler call when profiling is enabled, and to nothing when disabled

#ifdef PROFILING_ENABLED
#    define PROFILE_START(msg) currentProfiler().start(msg)
#    define PROFILE_END(msg)   currentProfiler().end(msg)
#else
#    define PROFILE_START(msg)
#    define PROFILE_END(msg)   
#endif

We have various different components in our framework, and I want to enable profiling in each component.

I'd like to be able to selectively enable profiling in each component.

My idea is to prefix all the profiler macros with the component's name, eg:

FOO_PROFILE_START(msg)
FOO_PROFILE_END(msg)

BAR_PROFILE_START(msg)
BAR_PROFILE_END(msg)

I could manually create

#ifdef ENABLE_FOO_PROFILING
#    define FOO_PROFILE_START(msg) PROFILE_START(msg)
#    define FOO_PROFILE_END(msg) PROFILE_END(msg)
#else
#    define FOO_PROFILE_START(msg) 
#    define FOO_PROFILE_END(msg) 
#endif

#ifdef ENABLE_BAR_PROFILING
#    define BAR_PROFILE_START(msg) PROFILE_START(msg)
#    define BAR_PROFILE_END(msg) PROFILE_END(msg)
#else
#    define BAR_PROFILE_START(msg) 
#    define BAR_PROFILE_END(msg) 
#endif

However, this is both tedious and error-prone.

Any time a new feature is added to the profiling framework I would have to find all my component specific macros and add a new macro to each of them.

What I'm looking for is a way to automatically generate the component-prefixed macros.

#ifdef ENABLE_FOO_PROFILING
    ADD_PREFIX_TO_ENABLED_PROFILING_MACROS(FOO)
#else
    ADD_PREFIX_TO_DISABLED_PROFILING_MACROS(FOO)
#endif

The net result of the above would be to create all the FOO_PROFILE_XXX macros I would have done manually.

Questions:

  • Is such a helper macro possible?
  • Is there a better way of achieving what I'm looking for?

I'm happy to use BOOST_PP if necessary.


Before posting this question I tried figuring this out myself, and the code I came up with follows, which may serve to show the road I was going down

#include <stdio.h>

#define PROFILE_START(msg) printf("start(%s)\n", msg);
#define PROFILE_END(msg)   printf("end(%s)\n", msg);

#define ENABLE(prefix) \
    #define prefix ## _PROFILE_START PROFILE_START \
    #define prefix ## _PROFILE_END   PROFILE_END

#define DISABLE(prefix) \
    #define prefix ## _PROFILE_START \
    #define prefix ## _PROFILE_END

#define ENABLE_FOO

#ifdef ENABLE_FOO
    ENABLE(FOO)
#else
    DISABLE(FOO)
#endif

#ifdef ENABLE_BAR
    ENABLE(BAR)
#else
    DISABLE(BAR)
#endif


int main()
{
    FOO_PROFILE_START("foo");
    FOO_PROFILE_END("foo");

    BAR_PROFILE_START("bar");
    BAR_PROFILE_END("bar");

    return 0;
}

Solution

  • Is such a helper macro possible?

    No. With the exception of pragmas, you cannot execute a preprocessing directive in a macro.

    You can do something very similar using pattern matching. By taking the varying parts out of the macro name, and putting it inside the macro itself, you can make a form that allows enabling/disabling for arbitrary names.

    This requires a tiny bit of preprocessor metaprogramming (which is a constant overhead; i.e., doesn't vary as you add modules), so bear with me.

    Part 1: A C preprocessor solution

    Using this set of macros:

    #define GLUE(A,B) GLUE_I(A,B)
    #define GLUE_I(A,B) A##B
    #define SECOND(...) SECOND_I(__VA_ARGS__,,)
    #define SECOND_I(_,X,...) X
    #define SWITCH(PREFIX_,PATTERN_,DEFAULT_) SECOND(GLUE(PREFIX_,PATTERN_),DEFAULT_)
    #define EAT(...)
    
    #define PROFILER_UTILITY(MODULE_) SWITCH(ENABLE_PROFILER_FOR_,MODULE_,DISABLED)
    #define PROFILER_IS_DISABLED ,EAT
    #define PROFILE_START_FOR(MODULE_, msg) SWITCH(PROFILER_IS_,PROFILER_UTILITY(MODULE_),PROFILE_START)(msg)
    #define PROFILE_END_FOR(MODULE_, msg)   SWITCH(PROFILER_IS_,PROFILER_UTILITY(MODULE_),PROFILE_END)(msg)
    

    ...which you can include in each module, you will gain the ability to do this:

    PROFILE_START_FOR(FOO,msg)
    PROFILE_END_FOR(FOO,msg)
    PROFILE_START_FOR(BAR,msg)
    PROFILE_END_FOR(BAR,msg)
    PROFILE_START_FOR(BAZ,msg)
    PROFILE_END_FOR(BAZ,msg)
    

    All of these macros, by default, expand to nothing; you can change this by defining ENABLE_PROFILER_FOR_xxx for any subset of FOO, BAR, or BAZ to expand to , (or ,ON if that looks better), in which case the corresponding macros will expand (initially, before your own macros come in) to PROFILE_START(msg)/PROFILE_END(msg); and the rest will continue expanding to nothing.

    Using the FOO module as an example, you can do this with a "control file": #define ENABLE_PROFILER_FOR_FOO ,ON; the command line: ... -DENABLE_PROFILER_FOR_FOO=,ON; or in a makefile; CFLAGS += -DENABLE_PROFILER_FOR_FOO=,ON.

    Part 2a: how it works; the SWITCH macro

    #define GLUE(A,B) GLUE_I(A,B)
    #define GLUE_I(A,B) A##B
    #define SECOND(...) SECOND_I(__VA_ARGS__,,)
    #define SECOND_I(_,X,...) X
    #define SWITCH(PREFIX_,PATTERN_,DEFAULT_) SECOND(GLUE(PREFIX_,PATTERN_),DEFAULT_)
    

    GLUE here is your typical indirect paste macro (allowing arguments to expand). SECOND is an indirect variadic macro returning the second argument.

    SWITCH is the pattern matcher. The first two arguments are pasted together, comprising the pattern. By default, this pattern is discarded; but due to the indirection, if that pattern is an object like macro, and that pattern's expansion contains a comma, it will shift a new second argument in. For example:

    #define ORDINAL(N_) GLUE(N_, SWITCH(ORDINAL_SUFFIX_,N_,th))
    #define ORDINAL_SUFFIX_1 ,st
    #define ORDINAL_SUFFIX_2 ,nd
    #define ORDINAL_SUFFIX_3 ,rd
    ORDINAL(1) ORDINAL(2) ORDINAL(3) ORDINAL(4) ORDINAL(5) ORDINAL(6)
    

    ...will expand to:

    1st 2nd 3rd 4th 5th 6th
    

    In this manner, the SWITCH macro behaves analogous to a switch statement; whose "cases" are object-like macros with matching prefixes, and which has a default value.

    Note that pattern matching in the preprocessor works with shifting arguments, hence the comma (the main trick being that of discarding unmatched tokens by ignoring an argument, and applying matched tokens by shifting a desired replacement in). Also for the most general case with this SWITCH macro, you need at a minimum to ensure that all PREFIX_/PATTERN_ arguments are pasteable (even if that token isn't seen, it has to be a valid token).

    Part 2b: combined switches for safety

    A lone switch works like a case statement, allowing you to shove anything in; but when the situation calls for a binary choice (like "enable" or "disable"), it helps to nest one SWITCH in another. That makes the pattern matching a bit less fragile.

    In this case, the implementation:

    #define PROFILER_UTILITY(MODULE_) SWITCH(ENABLE_PROFILER_FOR_,MODULE_,DISABLED)
    #define PROFILER_IS_DISABLED ,EAT
    #define PROFILE_START_FOR(MODULE_, msg) SWITCH(PROFILER_IS_,PROFILER_UTILITY(MODULE_),PROFILE_START)(msg)
    #define PROFILE_END_FOR(MODULE_, msg)   SWITCH(PROFILER_IS_,PROFILER_UTILITY(MODULE_),PROFILE_END)(msg)
    

    ...uses PROFILER_UTILITY as the inner switch. By default, this expands to DISABLED. That makes the pattern in SWITCH(PROFILER_IS_,PROFILER_UTILITY(MODULE_),PROFILE_START) by default be PROFILER_IS_DISABLED, which shoves in EAT. In the non-default case of PROFILER_UTILITY, the outer switch kicks in making it expand to PROFILE_START. PROFILE_END_FOR works analogously.

    The EAT macro takes (msg) in both cases to nothing; otherwise, the original macro's called.

    Is there a better way of achieving what I'm looking for?

    Depends on what you're looking for. This approach shows what's possible with the C preprocessor.