Difference between revisions of "X macros"

From vegard.wiki
Jump to navigation Jump to search
m (Vegard moved page The X macro to X macros)
(add category)
 
(One intermediate revision by the same user not shown)
Line 155: Line 155:
 
* https://philliptrudeau.com/blog/x-macro
 
* https://philliptrudeau.com/blog/x-macro
 
* https://en.wikipedia.org/wiki/X_Macro
 
* https://en.wikipedia.org/wiki/X_Macro
 +
* https://stackoverflow.com/questions/8662395/counting-preprocessor-macros
  
 
[[Category:Programming]]
 
[[Category:Programming]]
 
[[Category:C++]]
 
[[Category:C++]]
 +
[[Category:C preprocessor snippets]]

Latest revision as of 11:54, 1 March 2020

Introduction

Definitions:

#define _DEFINE_ENUM_NAME(name) name,
#define _DEFINE_ENUM_STR(name) #name,
#define DEFINE_ENUM(name) \
        enum name { \
                name(_DEFINE_ENUM_NAME) \
        }; \
        \
        const char *name##_names[] = { \
                name(_DEFINE_ENUM_STR) \
        }; \
        \
        const unsigned int nr_##name##s = sizeof(name##_names) / sizeof(*name##_names);

Usage:

#define opcode(X) \
        X(LOAD_CONSTANT) \
        X(LOAD_LOCAL) \
        X(STORE_LOCAL) \
        X(ADD) \
        X(SUB)

DEFINE_ENUM(opcode)

This defines enum opcode which contains LOAD_CONSTANT, etc. You can also map enum values to strings using the opcode_names array, like this:

void print_value(enum opcode opc)
{
        printf("%s\n", opcode_names[opc]);
}

Discontiguous values

For bitfields and cases where you need specific values, you can also modify the definition to allow specific/discontiguous enum values.

To avoid the name array from growing too large it is probably useful to use a map instead of an array.

#include <map>

#define _DEFINE_ENUM_NAME(name) name,
#define _DEFINE_ENUM_NAME_VALUE(name, value) name = value,

#define _DEFINE_ENUM_VALUE_STR1(name) { name, #name },
#define _DEFINE_ENUM_VALUE_STR2(name, value) { name, #name },

#define DEFINE_ENUM(name) \
        enum name { \
                name(_DEFINE_ENUM_NAME, _DEFINE_ENUM_NAME_VALUE) \
        }; \
        \
        std::map<enum name, const char *> name##s = { \
                name(_DEFINE_ENUM_VALUE_STR1, _DEFINE_ENUM_VALUE_STR2) \
        };

#define opcode(X, Y) \
        Y(LOAD_CONSTANT, 100) \
        X(LOAD_LOCAL) \
        Y(STORE_LOCAL,   200) \
        Y(ADD,           300) \
        X(SUB)

DEFINE_ENUM(opcode)

int main(int argc, char *argv[])
{
    for (auto &it: opcodes)
        printf("%d => %s\n", it.first, it.second);

    return 0;
}

This prints:

100 => LOAD_CONSTANT
101 => LOAD_LOCAL
200 => STORE_LOCAL
300 => ADD
301 => SUB

Serialization

You can use the same technique to define structs and also generate code to inspect them (reflection) or serialize them:

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

typedef char *char_ptr;

void serialize_char_ptr(const char *x)
{
        printf("\"%s\"\n", x);
}

void serialize_int(int x)
{
        printf("%d\n", x);
}

#define _DEFINE_STRUCT_MEMBER(type, name) type name;
#define _DEFINE_STRUCT_SERIALIZE(type, name) serialize_##type(x->name);

#define DEFINE_STRUCT(name) \
        struct name { \
                name(_DEFINE_STRUCT_MEMBER) \
        }; \
        \
        void serialize_##name(struct name *x) \
        { \
                name(_DEFINE_STRUCT_SERIALIZE) \
        }

#define player(X) \
        X(char_ptr, name) \
        X(int, age)

DEFINE_STRUCT(player);

int main(int argc, char *argv[])
{
        player vegard = {
                .name = strdup("Vegard"),
                .age = 32,
        };

        serialize_player(&vegard);
        return 0;
}

Output:

"Vegard"
32

See also