定义结构成员并匹配字符串常量而不重复

huangapple go评论82阅读模式
英文:

Defining struct members and matching string constants without repetition

问题

我正在尝试定义一个struct traits,其中包含不同特征的位域,如下所示:

struct traits
{
    unsigned char sometrait : 1;
    unsigned char someothertrait : 1;
    unsigned char anothertrait : 1;
};

为了简化定义新特征位域,我使用了这个宏:

#define __deftrait(name) unsigned char name : 1;

我还需要这些特征位域的字符串表示,因此我有另一个宏来定义这些字符串常量:

#define __def_trait_str_repr(name) const char* _##name##_str = #name;

我的traits.h如下所示:

#define __deftrait(name) unsigned char name : 1;
#define __def_trait_str_repr(name) const char* _##name##_str = #name;

typedef struct traits
{
    __deftrait(sometrait)
    __deftrait(someothertrait)
    __deftrait(anothertrait)
} traits;

__def_trait_str_repr(sometrait)
__def_trait_str_repr(someothertrait)
__def_trait_str_repr(anothertrait)

我在想能否让整个特征位域定义过程更清晰。__deftrait 宏可以处理定义位域成员和它们的字符串表示。

然而,我陷入了困境,因为我怀疑只是将字符串表示宏粘贴到定义宏的末尾会导致大量字符串副本,而静态字符串无法在结构体内部初始化。

因此,我想知道是否有一种方式可以定义某种宏来同时处理这两个任务,以便最终结果可以是:

typedef struct traits
{
    __magic(sometrait)
    __magic(someothertrait)
    __magic(anothertrait)   
} traits;
英文:

I am trying to define a struct traits, which contains bit-fields for different traits like so:

struct traits
{
    unsigned char sometrait : 1;
    unsigned char someothertrait : 1;
    unsigned char anothertrait : 1;
};

To simplify defining new trait bit-fields, I'm using this macro:

#define __deftrait(name) unsigned char name : 1;

I also need the string representations of these trait-bit fields, so I have another macro to define these string constants:

#define __def_trait_str_repr(name) const char* _##name##_str = #name;

My traits.h is as follows:

#define __deftrait(name) unsigned char name : 1;
#define __def_trait_str_repr(name) const char* _##name##_str = #name;

typedef struct traits
{
    __deftrait(sometrait)
    __deftrait(someothertrait)
    __deftrait(anothertrait)
    
} traits;

__def_trait_str_repr(sometrait)
__def_trait_str_repr(someothertrait)
__def_trait_str_repr(anothertrait)

I was wondering if I could make this whole trait bit-field defining process cleaner. The __deftrait macro could handle both defining the bit-field members, and their string representation.

However, I'm at my wits end here, because I suspect that just sticking the string representation macro at the end of the def macro would lead to a lot of string copies, and static strings can't be initialized inside a struct.

Thus, I was wondering if there is a way to define some kinda macro that would handle both, so the end result can be:

typedef struct traits
{
    __magic(sometrait)
    __magic(someothertrait)
    __magic(anothertrait)   
} traits;

答案1

得分: 2

你试图做的是一个常见的问题,下面的解决方案经常用于实现 "魔法枚举"。

#define LIST(...) \
  __VA_ARGS__(a) \
  __VA_ARGS__(b) \
  __VA_ARGS__(c)

#define DEFINE_MEMBER(...) unsigned char __VA_ARGS__ : 1;
#define STRINGIZE(...) const char * const __VA_ARGS__##_str = #__VA_ARGS__;

typedef struct traits
{
    LIST(DEFINE_MEMBER) /* 扩展为:
    unsigned char a : 1;
    unsigned char b : 1;
    unsigned char c : 1; */
} traits;

#undef E
#define E(...) const char * const __VA_ARGS__##_str = #__VA_ARGS__;

LIST(STRINGIZE) /* 扩展为
const char * const a_str = "a";
const char * const b_str = "b";
const char * const c_str = "c"; */

#undef STRINGISZE
#undef DEFINE_MEMBER
#undef LIST

基本思路是在顶部定义一个 LIST 宏,列出了符号及其所有属性。在这种情况下,只有名称。

然后,我们可以使用 LIST(DEFINE_MEMBER) 来扩展此列表以定义所有的位字段成员,并使用 LIST(STRINGIZE) 来定义我们的字符串常量。

注意:避免在全局范围内使用前导下划线。具有前导下划线的名称保留供实现使用。

C++ 特定注意事项

上述解决方案在 C 和 C++ 中都适用。
在 C++ 中,你可以省略 typedef,只需写 struct traits { ... };

此外,你可以通过将字符串常量声明为静态数据成员来简化此解决方案。@TedLyngmo 提供了一个示例。

C++11 或更高版本

在字符串常量上添加 constexpr,因为它们是编译时常量。

C++17 或更高版本

在常量上添加 inline constexpr,以给予常量内联链接。默认情况下,它们将具有内部链接,这可能会导致一些不寻常的 odr 违规。此外,即使你不能使用 std::string,你也可以考虑使用 std::string_view 而不是 char*,可能会更有用。

英文:

What you're trying to do is a common problem, and the following solution is often used for implementing "magic enums".

#define LIST(...) \
  __VA_ARGS__(a) \
  __VA_ARGS__(b) \
  __VA_ARGS__(c)

#define DEFINE_MEMBER(...) unsigned char __VA_ARGS__ : 1;
#define STRINGIZE(...) const char * const __VA_ARGS__##_str = #__VA_ARGS__;

typedef struct traits
{
    LIST(DEFINE_MEMBER) /* expands to:
    unsigned char a : 1;
    unsigned char b : 1;
    unsigned char c : 1; */
} traits;

#undef E
#define E(...) const char * const __VA_ARGS__##_str = #__VA_ARGS__;

LIST(STRINGIZE) /* expands to
const char * const a_str = "a";
const char * const b_str = "b";
const char * const c_str = "c"; */

#undef STRINGISZE
#undef DEFINE_MEMBER
#undef LIST

The basic idea is that we define a LIST macro at the top, which lists the symbols and all their properties. In this case, it's just the name.

We can then expand this list with LIST(DEFINE_MEMBER) to define all our bit-field members, and with LIST(STRINGIZE) to define our string constants.

Note: Avoid the use of leading underscores in global scope. Names with leading underscores are reserved for the implementation.

C++-specific notes

The above solution works in both C and C++.
In C++, you are free to omit typedef, can can just write struct traits { ... };

Also, you can simplify this solution by declaring the string constants as static data members. @TedLyngmo has provided an example.

C++11 or greater

Add constexpr to the string constants, since they are compile-time constants.

C++17 or greater

Add inline constexpr to give the constants inline linkage. By default, they would have internal linkage, which can cause some unusual odr-violations. Furthermore, even if you can't use std::string, you could use std::string_view instead of a char*, potentially.

huangapple
  • 本文由 发表于 2023年6月12日 04:07:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/76452329.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定