X macros

From vegard.wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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