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;
>Solution :
What you’re trying to do is a common problem, and the following solution is often used for implementing "magic enums".
#define LIST \
E(a) \
E(b) \
E(c)
#define E(...) unsigned char __VA_ARGS__ : 1;
typedef struct traits
{
LIST /* 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 /* expands to
const char * const a_str = "a";
const char * const b_str = "b";
const char * const c_str = "c"; */
#undef E
#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 then redefine how one of these entries E in the LIST macro is used, and then expand LIST. The first expansion produces all the bit-field members, the second expansion produces the global 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.