在 C++ 预处理器宏中扩展 __VA_ARGS__ 的 var 名称

Expanding var names of __VA_ARGS__ in C++ preprocessor macro

我正在尝试实现一个适当的 enum to str 宏,它将在代码中自动关联枚举值和它们的名称。

例如,我想像这样定义一个名为“Test”的新宏:

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

这样,通过调用 TestEnumToString(test2) 我可以访问字符串 "test2"。目前,我目前对这个 ENUM_STR_DECLARE 的实现是这样完成的:

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

虽然当我这样做时,__VA_ARGS__ 处理的枚举没有得到正确的名称分隔:

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

因为我想要 "test2", "test3" 而不是 "test2, test3"

有什么方法可以用逗号分隔扩展 #__VA_ARGS__ 名称吗?

干杯!

编辑:解决方案

这个问题似乎没有 easy/elegant 解决方案。迄今为止最好的实现是由@h-walters 提出的,它提供了一个非常详细的实现分解(感谢您的时间!)。这是他为感兴趣的人分享的link: H. Walters example.

然而,即使是这个实现似乎也必须限制您定义的枚举数量(在他的例子中是 9 个)。所以解决方法是我的最终答案。

与其苦苦寻找切片方式 __VA_ARGS__,不如像我一样简单地对整个 __VA_ARGS__ 进行字符串化,然后在每次我们想要获取特定名称时对获得的字符串进行切片枚举。显然,这是一种较慢的方法,因为它需要每次都解析整个字符串,而不是直接使用枚举索引获取。

这个速度问题与我无关,因为枚举到字符串的转换旨在用于显示目的,否则枚举将直接用于耗时的例程。如果您仍然希望在获取字符串翻译时更快,您可以利用缓存(例如 std::vector<std::string>),它可以在您第一次调用 toString() 方法时构建。或者你可以选择@h-walters 解决方案,这是最快的,知道它的局限性。

说得够多了,这是我最终得到的解决方案:

#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_)); }\
  }

因此,我上面给出的简单示例将展开为:

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

假设,如果您想获取枚举“test1”的字符串:

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

希望它能帮助其他开发者:)。

再次欢呼!

使用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)

演示:

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

不完全是解决方案,而是解决方法,缺点是您必须释放每个枚举:

#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] = '[=10=]';

    const char * d = ", [=10=]";
    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] = '[=10=]';
    }
    else {
        // first item
        size = strlen(tok);
        dest[0] = (char*)malloc(size + 1);
        memcpy(dest[0], tok, size);
        dest[0][size] = '[=10=]';

        // 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] = '[=10=]';
            ++i;
        }
    }

    free(srcCopy);

    return (const char**)dest;
}