lp0 on fire

My personal blog

C89-OOP: Type system

Downcasting and upcasting types can be hard because it's easy to lose track of which type what instance was. Since in most OOP languages we have a type system that helps with determinating the type at runtime, I thought it was a nice challenge to emulate this in C.

I found quite some research papers online on the topic, but I'm not smart enough to understand any of it so I wrote my own implementation without using any of the available papers for reference.

The goal is to provide a minimalistic system for identifying the type of an user-defined class instance. I tried to keep the implementation as simple as possible.

Type structure

typedef struct Type Type;

struct Type
{
    int (*getId)(void);
};

void Type_setId(int* typeId)
{
    static int id = 0;

    if (*typeId == 0)
    {
        *typeId = ++id;
    }
}

We make a Type VMT which contains a pointer to the function returning the instance's type id. The code for generating the new id speaks for itself.

Base type

I've modified Foo from C89-OOP: Class to demonstrate the new things we need to add in:

typedef struct Foo Foo;

struct Foo
{
    Type* type;
    int x;
};

int Foo_getTypeId()
{
    static int typeId = 0;

    Type_setId(&typeId);
    return typeId;
}

void Foo_ctor(Foo* self, int x)
{
    static Type typeinfo = {
        Foo_getTypeId
    };

    self->type = &typeinfo;
    self->x = x;
}

Foo_getTypeId generates a new id if it doesn't have one yet, and returns this id. In Foo_ctor we create the static instance of Foo's type information and assign it to all instances of Foo.

Derived type

This is the modified Bar class from C89-OOP: Single Inheritance. Like with Foo, quite a bit of code has been added.

typedef struct Bar Bar;

struct Bar
{
    Foo base;
    int y;
};

int Bar_getTypeId()
{
    static int typeId = 0;

    Type_setId(&typeId);
    return typeId;
}

void Bar_ctor(Bar* self, int x, int y)
{
    static Type typeinfo = {
        Bar_getTypeId
    };

    Foo_ctor(&self->base, x);
    self->base.type = &typeinfo;
    self->y = y;
}

We change the Type to match Bar in the constructor. This way if we cast Bar to Foo it would still report it to be Bar. This is the same behaviour as in C#.

Usage

The code isn't very clean, but it does illustrate how it works well:

Bar bar;
Foo* foo;
int isBarType;

Bar_ctor(&bar, 1, 2);
foo = (Foo*)&bar;
isBarType = foo->type->getId() == Bar_getTypeId();

To compare the types of two instances for equality:

Foo foo;
Bar bar;
int isBarType;

Foo_ctor(&foo, 1);
Bar_ctor(&bar, 1, 2);
isBarType = foo.type->getId() == bar.base.type->getId();

Conclusion

You now learned how to roll your own primitive type system. Even if this one isn't very complex, it is still quite a handful to write manually. It's not something you would need often, but when you need it you're thankful that you have it in an OOP language.