andersch.dev

<2022-05-19 Thu>

Reflection (Introspection)

Reflection refers to the ability of a programming language to examine & manipulate its type system at runtime. The term introspection on the other hand is constrained to the examination of types, so it's only a subset of the reflection system of a language.

Something like a serialization system that is robust to changing types can be written much easier when the language supports introspection.

Hypothetical introspection system in C

If C had introspection capabilities built-in to the language, the usage of that feature could look like this from the user site:

#include <introspection.h>

struct mystery_thing
{
    int   foo;
    char* stringy;
    float bar;
};
mystery_thing my_thing = {};

introspect_t inspect_struct = introspect(&my_thing);

unsigned int nr_members     = inspect_struct.member_len; // == 3
unsigned int type           = inspect_struct.type;       // == INTROSPECTION_TYPE_STRUCT

printf("%s %s has %u members", introspect_type_to_string(type), inspect_struct.name, nr_members);

for (int i = 0; i < nr_members; i++)
{
    introspect_t inspect_member = inspect_struct.members[i];
    switch (introspect_member.type)
    {
        case INTROSPECTION_TYPE_INTEGER:
        {
            printf("member %s is a %s", inspect_member.name, "integer");
        }
        case INTROSPECTION_TYPE_FLOAT:
        {
            printf("member %s is a %s", inspect_member.name, "float");
        }
        case INTROSPECTION_TYPE_STRING:
        {
            printf("member %s is a %s", inspect_member.name, "string");
        }
    }
}

/* CONSOLE OUTPUT */
// struct mystery_thing has 3 members
// member foo is a integer
// member stringy is a string
// member bar is a float

(Basic) Introspection in C via X-Macros

Some basic features that can be achieved with introspection can also be achieved with just the C preprocessor using X-Macros. The simplest use case is to turn an enum value to a corresponding string:

#define ALL_PRIMITIVES     \
  X(CIRCLE,   "Circle")    \
  X(TRIANGLE, "Triangle")  \
  X(QUAD,     "Quad")

enum primitive_type_e
{
#define X(name, str) PRIMITIVE_TYPE_##name,
  ALL_PRIMITIVES
#undef X
  PRIMITIVE_TYPE_COUNT
};

char *primitive_type_e_string_table[PRIMITIVE_TYPE_COUNT] =
{
#define X(name, str) str,
  ALL_PRIMITIVES
#undef X
};

const char* primitive_type_e_to_string(primitive_type_e type)
{
    switch(type)
    {
#define X(name, str) case PRIMITIVE_TYPE_##name : return str;
        ALL_PRIMITIVES
#undef X
    };
    return "INVALID";
}

Output of the preprocessor:

enum primitive_type_e
{

  PRIMITIVE_TYPE_CIRCLE, PRIMITIVE_TYPE_TRIANGLE, PRIMITIVE_TYPE_QUAD,

  PRIMITIVE_TYPE_COUNT
};

char *primitive_type_e_string_table[PRIMITIVE_TYPE_COUNT] =
{

  "Circle", "Triangle", "Quad",

};

const char* primitive_type_e_to_string(primitive_type_e type)
{
    switch(type)
    {

        case PRIMITIVE_TYPE_CIRCLE : return "Circle"; case PRIMITIVE_TYPE_TRIANGLE : return "Triangle"; case PRIMITIVE_TYPE_QUAD : return "Quad";

    };
    return "INVALID";
}

Introspection in C via a metaprogramming pre-pass

Resources