Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Defining struct members and matching string constants without repetition

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

#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.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading