1

I'm trying to implement a proper enum to str macro which would automatise the association between enum values and their name in the code.

For example I'd like to define a new macro called "Test" like this:

ENUM_STR_DECLARE(Test, 1, test1, test2, test3)

This way, by calling TestEnumToString(test2) I could get access to a string "test2". At the moment, my current implementation of this ENUM_STR_DECLARE is done as follows:

#define ENUM_STR_DECLARE(enumName, intOffset, v1, ...)\
  enum enumName { v1 =  intOffset, __VA_ARGS__};\
  const char *enumName##EnumStringArray[] = { #v1 ,#__VA_ARGS__};\
  const char *enumName##EnumToString(value) { return enumName##EnumStringArray[ value - offset ]; }

Although when I do that, the enums handled by __VA_ARGS__ doesn't get a proper name separation:

enum Test {
    test1 = 1, test2, test3
};
const char *TestEnumStringArray[] = {"test1", "test2, test3"};
const char *TestEnumToString(value) { return TestEnumStringArray[ value - offset]; }

as I'd like to have "test2", "test3" instead of "test2, test3".

Is there any way to expand #__VA_ARGS__ names with a comma separation?

Cheers!

Edit: A workaround proposal

It seems there is no easy/elegant solution for this problem. The best implementation so far was proposed by @h-walters which provided a pretty detailled break down of the implemntation (thanks for your time!). Here is the link he shared for those who are interested: H. Walters example.

However it seems even this implementation have to limit the number of enums you define (9 in his case). So a workaround has been the definitive answer for me.

Instead of struggling to get way of slicing __VA_ARGS__, one can simply stringify the whole __VA_ARGS__ as I did, and slice the obtained string each time we want to get the name of a specific enum. Obviously, this is a slower approach since it requires to parse the whole string each time instead of fetching directly with the enum index.

This speed issue is not relevant for me since the enum to string conversion is intended to be used for display purposes, otherwise the enum are directly used for time consuming routines. If you still wanna be fast while getting the string translation tho, you can take advantage of a cache (like a std::vector<std::string> for example) which could be built the first time you call the toString() method. Or you can go for @h-walters solution, which is the fastest, knowing its limitations.

Enough talking, here is the solution I finally end up with:

#define VA_TO_STR(...) #__VA_ARGS__

#define ENUM_STR_DECLARE(enumName_, intOffset_, v1_, ...)\
  enum enumName_ { v1_ =  intOffset_, __VA_ARGS__, enumName_##_OVERFLOW };\
  namespace enumName_##EnumNamespace{\
    const char *enumNamesAgregate = VA_TO_STR(v1_, __VA_ARGS__); \
    int enumOffSet = intOffset_; \
    std::string toString(int enumValue_) {\
      int commaCounter = 0; std::string outStr;\
      for( unsigned long iChar = 0 ; iChar < strlen(enumNamesAgregate) ; iChar++ ){ \
        if( enumNamesAgregate[iChar] == ',' ){\
          if( not outStr.empty() ){ return outStr; } /* found! return */\
          else{ commaCounter++; iChar += 2; } /* not yet found, next */ \
        }\
        if( commaCounter == enumValue_ ){ outStr += enumNamesAgregate[iChar]; }\
      }\
      return outStr;\
    }\
    std::string toString(enumName_ enumValue_){ return enumName_##EnumNamespace::toString(static_cast<int>(enumValue_)); }\
    int toEnumInt(const std::string& enumStr_){\
      for( int enumIndex = intOffset_ ; enumIndex < enumName_::enumName_##_OVERFLOW ; enumIndex++ ){ \
        if( enumName_##EnumNamespace::toString(enumIndex) == enumStr_ ){ return enumIndex; } \
      }\
      return intOffset_ - 1; /* returns invalid value */\
    }\
    enumName_ toEnum(const std::string& enumStr_){ return static_cast<enumName_>(enumName_##EnumNamespace::toEnumInt(enumStr_)); }\
  }

Thus, the simple example I presented above would unfold as:

enum Test {
  test1 = 1, test2, test3, Test_OVERFLOW
};
namespace TestEnumNamespace {
  const char *enumNamesAgregate = "test1, test2, test3";
  int enumOffSet = 1;
  std::string toString(int enumValue_) {
    int commaCounter = 0;
    std::string outStr;
    for (unsigned long iChar = 0; iChar < strlen(enumNamesAgregate); iChar++) {
      if (enumNamesAgregate[iChar] == ',') {
        if (notoutStr.empty()) { return outStr; }
        else {
          commaCounter++;
          iChar += 2;
        }
      }
      if (commaCounter == enumValue_) { outStr += enumNamesAgregate[iChar]; }
    }
    return outStr;
  }
  std::string toString(Test enumValue_) { return TestEnumNamespace::toString(static_cast<int>(enumValue_)); }
  int toEnumInt(const std::string &enumStr_) {
    for (int enumIndex = 1; enumIndex < Test::Test_OVERFLOW; enumIndex++) {
      if (TestEnumNamespace::toString(enumIndex) == enumStr_) { return enumIndex; }
    }
    return 1 - 1;
  }
  Test toEnum(const std::string &enumStr_) { return static_cast<Test>(TestEnumNamespace::toEnumInt(enumStr_)); }
}

So let's say, if you want to get the string of the enum "test1":

TestEnumNamespace::toString(Test::test1);
TestEnumNamespace::toEnum("test1");

Hope it will help other dev :).

Cheers again!

nadrino
  • 103
  • 6

2 Answers2

1

Using boost preprocessor:

#include <boost/preprocessor/tuple.hpp>
#include <boost/preprocessor/seq.hpp>
#include <boost/preprocessor/stringize.hpp>

#define MACRO_DELIM(r, data, elem) BOOST_PP_STRINGIZE(elem) ,

#define DELIMITED_STRINGIZE(...) \
    BOOST_PP_SEQ_FOR_EACH(MACRO_DELIM, _, \
        BOOST_PP_TUPLE_TO_SEQ((__VA_ARGS__)))
    
#define ENUM_STR_DECLARE(enumName, intOffset, v1, ...)\
  enum enumName { v1 =  intOffset, __VA_ARGS__};\
  const char *enumName##EnumStringArray[] = { DELIMITED_STRINGIZE(v1, __VA_ARGS__)};\
  const char *enumName##EnumToString(value) { return enumName##EnumStringArray[ value - offset ]; }

ENUM_STR_DECLARE(Test, 1, test1, test2, test3)

Demo:

http://coliru.stacked-crooked.com/a/3f3376f2d4abe266

H Walters
  • 2,634
  • 1
  • 11
  • 13
  • Thanks for your suggestion! Is there a way I could minimally expand boost macros to get rid of its dependency? – nadrino Apr 02 '21 at 13:34
  • If you don't want boost, you might try using `TUPLE` (and `MAKE_INITIALIZER`) from [here](https://stackoverflow.com/questions/44479282/preprocessor-concatenate-string-to-each-argument-in-va-args/44479664#44479664) . – H Walters Apr 02 '21 at 19:32
  • Thanks :) I'm not sure how to use these, it is a bit cryptic to me! Could you draw an example? – nadrino Apr 04 '21 at 17:31
  • Like [this](http://coliru.stacked-crooked.com/a/ad6109014a3592bc); I'll not post this as an answer since it's already using the other answer. – H Walters Apr 04 '21 at 19:45
1

Not exactly solution, but workaround, downside is you have to free each enum:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char** explode(const char* src, size_t & out_size);

#define CAT(...) #__VA_ARGS__
#define ENUM_STR_DECLARE(enumName, intOffset, v1, ...)\
    enum enumName { v1 =  intOffset, __VA_ARGS__};\
    const char *enumName##EnumString = CAT(v1, __VA_ARGS__);\
    size_t enumName##EnumStringArraySize = 0;\
    const char **enumName##EnumStringArray = explode(enumName##EnumString, enumName##EnumStringArraySize);\
    const char *enumName##EnumToString(size_t value) { return enumName##EnumStringArray[ value - intOffset ]; }\
    void free##enumName() {\
        for (size_t i = 0; i < enumName##EnumStringArraySize; ++i) {\
            free((char *)enumName##EnumStringArray[i]);\
        }\
        free(enumName##EnumStringArray);\
    }

ENUM_STR_DECLARE(Test, 3, test1, test2, test3, test4, test5, test6)

int main() {
    printf("test1: %s, test3: %s, test6: %s", TestEnumToString(test1), TestEnumToString(test3), TestEnumToString(test6));

    freeTest();
    return 0;
}

const char** explode(const char* src, size_t & out_size)
{
    size_t size = strlen(src);
    if (!size) return NULL;

    int i = 0;
    char* psrc = (char*)src;
    for (; psrc[i]; (psrc[i] == ',' ? i++ : *psrc++));

    char** dest = (char**)malloc(sizeof(char*) * ++i);
    out_size = i;

    char* srcCopy = (char*)malloc(size + 1);
    memcpy(srcCopy, src, size);
    srcCopy[size] = '\0';

    const char * d = ", \0";
    char* tok = strtok(srcCopy, d);
    if (tok == NULL) {
        // only one item
        dest[0] = (char*)malloc(size + 1);
        memcpy(dest[0], src, size);
        dest[0][size] = '\0';
    }
    else {
        // first item
        size = strlen(tok);
        dest[0] = (char*)malloc(size + 1);
        memcpy(dest[0], tok, size);
        dest[0][size] = '\0';

        // rest
        i = 1;
        while ((tok = strtok(NULL, d)) != NULL) {
            size = strlen(tok);
            dest[i] = (char*)malloc(size + 1);
            memcpy(dest[i], tok, size);
            dest[i][size] = '\0';
            ++i;
        }
    }

    free(srcCopy);

    return (const char**)dest;
}
Kazz
  • 1,030
  • 8
  • 16