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"; }