0

I'd like to create a macro that spits out an existent constant.

There are multiple constants and they all follow the form PREFIX_COMPONENT_ERROR.

Example code:

#include <stdlib.h>

enum {
  MODULE_ERROR_COMP1_ERROR1 = 0,
  MODULE_ERROR_COMP1_ERROR2,
  MODULE_ERROR_COMP1_ERROR3,
  MODULE_ERROR_COMP2_ERROR1,
  MODULE_ERROR_COMP2_ERROR2,
  MODULE_ERROR_COMP2_ERROR3,
};

static void* some_function (const char *restrict input);

#define EMPTY(...)
#define DEFER(...) __VA_ARGS__ EMPTY()
#define OBSTRUCT(...) __VA_ARGS__ DEFER(EMPTY)()
#define EXPAND(...) __VA_ARGS__
#define PASTER(x, y) x ## _ ## y

#define MODULE_ERROR some_function ("module error")

#define ERROR_PREFIX DEFER(MODULE_ERROR)
#define GENERATE_ERROR_(prefix, component, error) PASTER(DEFER(PASTER(prefix, component)), error)
#define GENERATE_ERROR(prefix, component, error) EXPAND(GENERATE_ERROR_(prefix, component, error))

static void* some_function (const char *restrict input) {
  /*
   * Does something with the input and returns some data,
   * but for simplicity let's assume it just passes the input through.
   */
  return (input);
}

int main (int argc, char **argv) {
  /* Generate enum via PP macros, for instance: */
  for (size_t i = 0; 3 > i; ++i) {
    int error_code = 0;
    void *common_error = ERROR_PREFIX;

    if (0 == i) {
      error_code = GENERATE_ERROR (ERROR_PREFIX, COMP1, ERROR1);
    }
    else if (1 == i) {
      error_code = GENERATE_ERROR (ERROR_PREFIX, COMP2, ERROR3);
    }
    else {
      error_code = GENERATE_ERROR (ERROR_PREFIX, COMP2, ERROR2);
    }

    /* Do something with error_code and common_error. */
  }

  return (EXIT_SUCCESS);
}

I wonder if what I want to do is even possible with simple PP directives in the first place.

The first issue is that I cannot use the concatenation macro PASTER twice, because the PP always chokes on the inner PASTER() call.

Converting that to a three-parameter macro such as:

#define PASTER(x, y, z) x ## _ ## y ## _ ## z

#define GENERATE_ERROR_(prefix, component, error) PASTER(prefix, component, error)

will expand prefix, which I do not want to happen and will run into an error anyway.

Using something like PASTER(OBSTRUCT(prefix), component, error) will likewise fail on concatenation.

I'm fully aware that this WOULD work, but I don't like it:

#define MODULE_ERROR_FUNC some_function ("module error")

#define ERROR_PREFIX MODULE_ERROR
#define GENERATE_ERROR_(prefix, component, error) PASTER(prefix, component, error)
#define GENERATE_ERROR(prefix, component, error) GENERATE_ERROR_(prefix, component, error)

/* [...] */
    void *common_error = MODULE_ERROR_FUNC;

Is there really no way to let ERROR_PREFIX be expanded only once in the GENERATE_ERROR macro (i.e., to MODULE_ERROR) and otherwise have it expand to the function call (i.e., some_function ("module error"))?

4
  • What's wrong with #define GENERATE_ERROR(prefix, component, error) prefix ## _ ## component ## _ ## error — and then invoking GENERATE_ERROR(MODULE_PREFIX, COMP1, ERROR1)? Commented Apr 6, 2020 at 19:44
  • That would work, I guess, because concatenation disables further expansion and there is no indirection that would force it. My intention was to avoid having to use the full prefix in my case, though, because it's so long and shortening via another macro would be beneficial. It's not obvious in this example, but when the prefix is something like PROJECT_LONG_SUBMODULE_ERROR you'd like to avoid typing it all the time, especially if you use macros internally anyway. Commented Apr 6, 2020 at 20:39
  • Can you not use #define GENERR(component, error) GENERATE_ERROR(PROJECT_LONG_SUBMODULE_ERROR, component, error) to shorten the long names — invoking GENERR(COMPONENT2, ERROR2) to get PROJECT_LONG_SUBMODULE_ERROR_COMPONENT2_ERROR2 as the error name? Commented Apr 6, 2020 at 20:56
  • Yep, that seems to work. If you'd like to add this as a proper answer and maybe also explain why PROJECT_LONG_SUBMODULE_ERROR won't be expanded during the expansion of GENERR (i.e., why is it painted blue in that context?) I'd accept it. :) Commented Apr 6, 2020 at 22:03

1 Answer 1

1

My immediate reaction to seeing your question was "you're over-complicating it!" For example, what's wrong with:

#define GENERATE_ERROR(prefix, component, error) prefix ## _ ## component ## _ ## error

and then invoking:

GENERATE_ERROR(MODULE_PREFIX, COMP1, ERROR1)

One partial answer to "what's the problem?" is that "it's not obvious in this example, but when the prefix is something like PROJECT_LONG_SUBMODULE_ERROR you'd like to avoid typing it all the time". Which is OK, but then you could consider:

#define GENERR(component, error) GENERATE_ERROR(PROJECT_LONG_SUBMODULE_ERROR, component, error)

to shorten the long names — invoking

GENERR(COMPONENT2, ERROR2)

to get the error name:

PROJECT_LONG_SUBMODULE_ERROR_COMPONENT2_ERROR2

Why won't PROJECT_LONG_SUBMODULE_ERROR be expanded during the expansion of GENERR (i.e., why is it painted blue in that context)?

It isn't painted blue; I just wouldn't create a macro with that name so there'd be nothing for it to expand to — just like there shouldn't be a macro COMPONENT2 or ERROR2 that can be expanded when GENERR is invoked.


Experimentation

Consider the source file pp31.c:

#define GENERATE_ERROR(prefix, component, error) prefix ## _ ## component ## _ ## error
GENERATE_ERROR(MODULE_PREFIX, COMP1, ERROR1)    -- OK

#define GENERR(component, error) GENERATE_ERROR(PROJECT_LONG_SUBMODULE_ERROR, component, error)
GENERR(COMPONENT2, ERROR2)                      -- OK

#define PLS_ERR PROJECT_LONG_SUBMODULE_ERROR

#undef GENERR
#define GENERR(component, error) GENERATE_ERROR(PLS_ERR, component, error)
GENERR(COMPONENT2, ERROR2)                      -- Broken

#undef GENERR
#define GENERR_PREFIX(prefix, component, error) GENERATE_ERROR(prefix, component, error)
#define GENERR(component, error) GENERR_PREFIX(PLS_ERR, component, error)
GENERR(COMPONENT2, ERROR2)                      -- OK

#define PROJECT_LONG_SUBMODULE_ERROR pink_elephant
GENERR(COMPONENT2, ERROR2)                      -- Broken (too many pink elephants)

#undef PROJECT_LONG_SUBMODULE_ERROR
#define GENCOMPERR(err) GENERATE_ERROR(PLS_ERR, COMPONENT2, err)
GENCOMPERR(ERROR2)                              -- Broken

#undef GENCOMPERR
#define GENCOMPERR(err) GENERR(COMPONENT2, err)
GENCOMPERR(ERROR2)                              -- OK

#undef GENCOMPERR
#define CURR_COMP COMPONENT2
#define GENCOMPERR(err) GENERATE_ERROR(PLS_ERR, CURR_COMP, err)
GENCOMPERR(ERROR2)                              -- Broken

#undef GENCOMPERR
#define GENCOMPERR(err) GENERR_PREFIX(PLS_ERR, CURR_COMP, err)
GENCOMPERR(ERROR2)                              -- OK

When I run cpp pp31.c (this is the cpp from GCC 9.3.0 running on a Mac), I get a few more blank lines than are shown below, but the non-blank lines are the interesting ones anyway:

# 1 "pp31.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "pp31.c"

MODULE_PREFIX_COMP1_ERROR1 -- OK

PROJECT_LONG_SUBMODULE_ERROR_COMPONENT2_ERROR2 -- OK

PLS_ERR_COMPONENT2_ERROR2 -- Broken

PROJECT_LONG_SUBMODULE_ERROR_COMPONENT2_ERROR2 -- OK

pink_elephant_COMPONENT2_ERROR2 -- Broken (too many pink elephants)

PLS_ERR_COMPONENT2_ERROR2 -- Broken

PROJECT_LONG_SUBMODULE_ERROR_COMPONENT2_ERROR2 -- OK

PLS_ERR_CURR_COMP_ERROR2 -- Broken

PROJECT_LONG_SUBMODULE_ERROR_COMPONENT2_ERROR2 -- OK

The key point is that (with care), you can have macros that expand to the elements of the final concatenated error message name (hence PLS_ERR and CURR_COMP), but you cannot have macros for those elements (no 'pink elephants').


I reserve judgement on whether this is truly a good design for handling error message names, but I think it can be made to work.

Sign up to request clarification or add additional context in comments.

3 Comments

The part I do not understand is that my compiler/preprocessor (part of GCC 8.3.0) does not expand PROJECT_LONG_SUBMODULE_ERROR even though it is a macro for pink_elefant (or some_function(...)). Likewise, the PLS_ERR example won't work directly because PLS_ERR is not getting expanded. I haven't yet understood why that is the case.
No arrogance implied. If I read ISO/IEC 9899:TC2 6.10 correctly, GENERR(a, b) should be substituted as such: GENERATE_ERROR(PLS_ERR, a, b)GENERATE_ERROR(PROJECT_LONG_SUBMODULE_ERROR, a, b)PROJECT_LONG_SUBMODULE_ERROR ## _ ## a ## _ ## b because arguments and other parameters are substituted before other function-like macros, but this doesn't seem to happen. The indirection SHOULD create a new expansion context, but that doesn't seem to be true and I cannot find any (obvious?) reason in the C99 standard.
I've put together a file pp31.c which is in the answer with numerous variations on the theme, some of which work, some of which don't (and which is which is documented, and illustrated). I hope the extra information helps.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.