4

Is there a way to reverse #define instruction?

In the following example

#define ZERO 0
#define ONE 1
#define TWO 2
#define THREE 3

is it possible to retrieve TWO from the integer value 2?

This example comes from a C code but I can use some C++ code if needed. My goal is to be able to factorize some spurious switch-case loops of this form :

switch(num)
{
   case ZERO:
      return std::to_string(foo.V_ZERO);
   case ONE:
      return std::to_string(foo.V_ONE);
   case TWO:
      return std::to_string(foo.V_TWO);
   case THREE:
      return std::to_string(foo.V_THREE);
}

where foo is an instance of a structure like this:

struct Foo
{
   union Val
   { 
      int V_ZERO;
      int V_ONE;
      double V_TWO; // nonsense: just to say that types are not the same
      int V_THREE;
   };
};

My constraints are the following:

  1. I can't remove the functionality provided by #define, i.e. I can write something equivalent, e.g. an enumeration, but I can't lose the map between ZERO and 0, ONE and 1 etc;
  2. the existing code is written in C and I can't rewrite it in C++. However, I can write some supplementary C++ code.

I have some ideas to simplify the code but I wonder if there is a very known elegant way of doing this, especially by means of some templates or preprocessor directives.

EDIT: usage of std::to_string added to say that I am not interested in knowing how to convert or handle multiple types from a union.

Aleph
  • 1,343
  • 1
  • 12
  • 27
  • Please, define _Elegant way_ in programming. When one uses `PUSH ADDR; RET` just to make an indirect subroutine call, should it be called _elegant?_ or not? – Luis Colorado Feb 24 '18 at 09:13

5 Answers5

3

If you want to automate some of the process, one option would be to use xmacros. They are hacky, but depending on your code size might make maintenance (as in, adding new entries) easier, at the expense of the rage of your fellow co-workers:

Define the xmacro list:

#define XLIST(xmacro) \
   xmacro(ZERO, 0) \
   xmacro(ONE, 1) \
   xmacro(TWO, 2) \
   xmacro(THREE, 3) \

And then use it whenever you want to iterate through all items:

// create an enum containing all items
#define xmacro(name, value) name,
enum Item
{
    XLIST(xmacro)
};
#undef xmacro

// get the number of items
#define xmacro(name, value) +1
const int NumberOfItems = 0 XLIST(xmacro);
#undef xmacro

// enum -> value
int itemToValue(enum Item item)
{
    switch (item)
    {
        // create a mapping from enum to x
#define xmacro(name, value) case name: return value;
        XLIST(xmacro)
#undef xmacro
    }

    return -1;
}

// get enum name
const char * getItemName(enum Item item)
{
    switch (item)
    {
        // create a mapping from enum to x
#define xmacro(name, value) case name: return #name;
        XLIST(xmacro)
#undef xmacro
    }

    return NULL;
}

This will get preprocessed into something like:

enum Item
{
    ZERO,
    ONE, 
    TWO, 
    THREE,
};

const int NumberOfItems = 0 +1 +1 +1 +1; // == 4

int itemToValue(enum Item item)
{
    switch (item)
    {
        case ZERO: return 0; 
        case ONE: return 1; 
        case TWO: return 2; 
        case THREE: return 3;   
    }

    return -1;
}

const char * getItemName(enum Item item)
{
    switch (item)
    {
        case ZERO: return "ZERO"; 
        case ONE: return "ONE"; 
        case TWO: return "TWO"; 
        case THREE: return "THREE";

    }

    return NULL;
}

You can create just about any mapping you'd like from this, i.e. for your struct you would use something similar to what @Jean-François wrote:

// get struct value by item type
double getValueByName(enum Item item, struct Foo values)
{
    switch (item)
    {
        // create a mapping from enum to x
#define xmacro(name, value) case name: return values.V_##name;
        XLIST(xmacro)
#undef xmacro
    }

    return -1;
}
vgru
  • 49,838
  • 16
  • 120
  • 201
  • Thank you very much Groo. I have ever heard of X macros but I have never used them. Let me try to use them first and I will reply to you after. – Aleph Feb 23 '18 at 10:40
  • 1
    @Aleph: the syntax of x-macros can be confusing to unsuspecting developers, so make sure you don't turn them into a habit. :) Note also that you can provide any additional metadata inside the x-macro list, i.e. `xmacro(ZERO, 0, int)`, `xmacro(TWO, 2, double)`, most list invocations won't use all the parameters, but some might, i.e. (`xmacro(name, value, type) "type of " #name " is " #type`). – vgru Feb 23 '18 at 10:47
  • Everything compiles except the computation of the number of items. I get the following error message: "test.cpp:35:25: error: expected initializer before numeric constant const int NumberOfItems 0 XLIST(xmacro);" – Aleph Feb 23 '18 at 14:33
  • It must be a typo but I don't know preprocessor directives enough to fix it. – Aleph Feb 23 '18 at 14:34
  • @Aleph: sorry, it's my typo, it should be `const int NumberOfItems = 0+1+1+1...;` (it's missing the assignment operator `=`) This is one of the downsides of using macros, often a small typo like this gets hard to detect. However, most compilers allow to to dump the preprocessor output, i.e. how the compiler sees your code after doing all the macro expansion and replacement, so this can often help you identify the issue. – vgru Feb 23 '18 at 15:37
  • Thank you it works fine now. I don't manage to call getValueByName in a toy example (simple body of a main function). I get '4196064.000000' as return value of getValueByName(ZERO, foo). Could you provide an example of usage please? Sorry, I am not a preprocessor expert at all. – Aleph Feb 23 '18 at 15:50
  • @Aleph: this function expands to `switch (item) { case ZERO: return values.V_ZERO; /* followed by other cases */ }`, so I would say that `V_ZERO` has the value `4196064` which is implicitly cast to a `double`. Whenever you see `XLIST(xmacro)` in the code, it's expanded into a list using the last definition of `xmacro(x, y)`. So `XLIST(xmacro)` in the last case will render `case name: return values.V_##name;`, for all `name` parameters in the initial list. – vgru Feb 23 '18 at 15:57
1

No, that's not possible.

Preprocessor #defines are textually replaced by their definitions by the preprocessing phase. The actual compiler never sees the symbols. What do you feel that TWO is, at run-time? It's already been replaced by the integer literal 2 everywhere, so there's no change.

const int two_define = TWO;
const int two_literal = 2;

will both put the integer value 2 in the respective variables, there's no magic smell that somehow differentiates the TWO from 2. The compiler will see const int two_define = 2; since the preprocessor symbol will be gone.

Also: what is the actual return type of the function, given that you're returning both ints and doubles? That would imply that it's double.

unwind
  • 391,730
  • 64
  • 469
  • 606
  • You're right unwind. I try to find some alternative solution actually. Don't worry about the return type. Let say that all types are converted in a string at the very end of the function in order simplify the problem. However, union's types may be very different (int, double, array, char*, struct, ...) – Aleph Feb 23 '18 at 10:45
1

In a general case you have to resort to something like "X macros", as proposed in another answer. But this is the last resort when everything else has failed. You'll have to go there if the numbers are completely arbitrary.

However, in this specific case the numbers are adjacent and starting from zero. That calls for enum combined with a look-up table. The standard way to implement it is like this:

#include <stdio.h>

typedef enum
{
  ZERO,
  ONE,
  TWO,
  THREE,
  SUPPORTED_NUMBERS
} number_t;

const char* STR_NUMBER [] =
{
  "ZERO",
  "ONE",
  "TWO",
  "THREE",
};

_Static_assert((sizeof STR_NUMBER / sizeof *STR_NUMBER) == SUPPORTED_NUMBERS,
               "Error: enum does not correspond to look-up table.");

int main (void)
{
  for(number_t i=0; i<SUPPORTED_NUMBERS; i++)
  {
    printf("%d %s\n", i, STR_NUMBER[i]);
  }
}
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • +1; Still I'd create the lookup table with xmacros, too, makes maintenance easier. Additionally, I'd use designated initializers (unless you can't for C++ compatibility...). This would even handle gaps and aliases (retaining the last alias as final name) in the enum -- and yes, I am aware, this is not suitable for *every* enum... – Aconcagua Feb 23 '18 at 10:45
  • Thank you Lundin. Can you explain me the static assertion because I don't get it? – Aleph Feb 23 '18 at 10:49
  • @Aconcagua Regarding designated initializers and enums, you might find this interesting: https://stackoverflow.com/questions/43043246/how-to-create-type-safe-enums – Lundin Feb 23 '18 at 11:02
  • 1
    @Aleph It verifies that the amount of enum items correspond to the amount of array items. The last item in the enum, `SUPPORTED_NUMBERS`, is only there as a counter. In my example it will be 4. The static assert checks that the array created also contains 4 items. Alternatively you could do `const char* STR_NUMBER [SUPPORTED_NUMBERS] = ...` but that doesn't protect from having too few items in the array, so that's a worse solution. – Lundin Feb 23 '18 at 11:03
  • Oh yes I get it! Simple but smart! – Aleph Feb 23 '18 at 11:06
  • I don't see how to use your solution in my code. I would like to be able to perform some concatenation in order to access fields from STR_NUMBER[i] (roughly speaking something close to foo.V_##STR_NUMBER[i]). Do you know if it is possible? – Aleph Feb 23 '18 at 14:48
  • 1
    @Aleph Short of "x macros", another look-up table. `const size_t V_ITEM[] = { offsetof(union Val, V_ZERO), offsetof( ...`. Then point a `uint8_t` pointer to `(uint8_t*)&the_struct + V_ITEM[i]`. Rather ugly though, but so are using "variants" in the first place. I suspect the root of the problem is the underlying program design, there's surely better solutions to whatever problem you are trying to solve, than using variants. – Lundin Feb 23 '18 at 14:57
  • Oh yes there is a BIG design problem in this code and this is not the only one. Unfortunately, I must work with it. I dream every night that I recode everything in C++... ;) – Aleph Feb 23 '18 at 15:10
  • @Aleph So you have to chose between x macros or template metaprogramming. Now that's a plague vs cholera choice right there :) – Lundin Feb 23 '18 at 15:29
0

For those 2 questions:

Is there a way to reverse #define instruction?

is it possible to retrieve TWO from the integer value 2?

Maybe array of mapping structures is suitable for you:

#define ZERO 0
#define ONE 1
#define TWO 2
#define THREE 3

typedef struct
{
    int number;
    const char *name;
} name_map_t;


#define MAP_NAME_STR(id) { id, #id },


static const name_map_t name_map_table[] =
{
    MAP_NAME_STR( ZERO  )
    MAP_NAME_STR( ONE   )
    MAP_NAME_STR( TWO   )
    MAP_NAME_STR( THREE )
};

Then just loop name_map_table array to find wanted counterpart.

Community
  • 1
  • 1
SKi
  • 8,007
  • 2
  • 26
  • 57
0

I identify your problem, and I'll show yo what I do when I face this kind of Enumeration pattern:

Let's say I have an enumeration of values, which I want to print, to switch, to represent array indices, etc.

first of all I write an include file that will support all kinds of uses I'm going to do of this enum type:

enum(INITIAL_STATE, 0, "This is the initial state")
enum(FLAG_READ, 1, "We have read the flag symbol")
...

then i use different macro definitions to expand this dataset of registers, as follows:

struct enum MyEnum {
#define enum(val,ix, string) val,
#include "myenumdef.i"
#undef enum
};

/* then, later in the same file ... */

char *MyEnumStrings[] = {
#define enum(val,ix, string) string,
#include "myenumdef.i"
#undef enum
};

/* .... */

char *MyEnumNames[] = {
#define enum(val, ix, string) #val, /* the string equivalent of enum names */
#include "myenumdef.i"
#undef enum
};

/* and more complex forms... like */

struct myEnumDesc {
    int   e_val;
    int   e_ix;
    char *e_name;
    char *e_desc;
} enum_table[] = {
#define enum(val,ix,string) val, ix, #val, string,
#include "myenumdef.i"
#undef enum
}; /* enum_table */


/* ... even, when I want to switch on them */
    switch(val) {
#define enum(val,ix,string) case VAL_##val: return string"("#val"="#ix")";
#include "myenumdef.i"
#undef enum
    default: return "invalid val";
    } /* switch */

Just prepare an example with the above, and run it through the C preprocessor, to see how the final C code looks like.

And you can do this as many times I need (even in a single file), I only need to change how the macro enum (in this case, you can have even several macro names to define and use them to construct more complex data dependencies) before including the data file. In case you want to add a constant to the enum, you have only to add it to the .i file, encoded as it appears above, and everything will adapt for the change.

you asked for C-only solution, so I give the more aproximate way I know about how to deal with the different definitions for the same set of items.

Luis Colorado
  • 10,974
  • 1
  • 16
  • 31