This is a neat trick I learned about in the Clean Architecture book by Robert C. Martin.1

Polymorphism in object-oriented languages

You are probably familiar with the concept of polymorphism from an object oriented language like C++ or Java. It is used in many design patterns and is an esential tool for writing decent code. The idea is using an abstract interface to make a concrete function call. The function to call is determined by the system at runtime.

Take a look at the following implementation:

class my_abstract_class {
    public:
        virtual void foo() = 0;
};

class my_concrete_class: public my_abstract_class {
    public:
        virtual void foo() override {
            cout << "this is the concrete class" << endl;
    };
};

In this C++ code, the my_concrete_class is an implementation of the my_abstract_class. The = 0 is a way to mark an abstract method - a class containing one or more abstract methods is also abstract, which means it cannot be instantiated. Note, however, that this is not necessary for polymorphism to work. It could very well be that the base class just contains a method with an empty body. The abstract function is used to prevent the programmer from accidentaly instantiating a base class that is not supposed to be instantiated. Similarly, the override keyword simply prevents the compiler from compiling the code if a method from the base class isn’t actually overriden by this implementation, but serves no other purpose.

What is needed, however, is the virtual keyword preceding the function in the base class. This routes the function call made at runtime through something called the v-table, where a pointer to the corresponding implementation of the function is stored. One does not need to understand the implementation of polymorphism to use it effectively, but the fact that it’s a sophisticated abstraction over function pointers will be a useful thing to remember for later.

I use the keyword virtual in the my_concrete_class for stylistical purposes, but it’s not needed here.

C++ specifics aside, you should get the idea what is the esence of polymorphism from the following code snippet:

int main() {
    my_abstract_class * interface = new my_concrete_class;
    interface->foo();
}

Output:

this is the concrete class

Note that I used a pointer to my_abstract_class, instantiated it with a pointer to an instance of the my_concrete_class, made a function call from the interface of the my_abstract_class and the correct implementation was called. This is really all there is to it.

If you still don’t fully grasp this concept, I suggest going through the explanation here.

Well, what about plain C? There are no objects to begin with, let alone inheritance or virtual methods. So how do we achieve polymorphic behaviour there? Well, all we need to do is use a cool little trick with function pointers.

Implementation in plain C

In plain C, we can only use a struct. Unlike C++, a C struct is just a collection of variables without a constructor or methods. But if we want to use it as a combination of data and logic (which is many times somewhat incorrectly used to describe what object-oriented programming is about, as Robert C. Clark explains) we absolutely can. Function pointers to the rescue!

Now this C++ code:

class my_abstract_class {
    public:
        virtual void foo() = 0;
};

Becomes this in C

struct my_abstract_struct {
    void(*foo)();
};

And the concrete class:

class my_concrete_class: public my_abstract_class {
    public:
        virtual void foo() override {
            cout << "this is the concrete class" << endl;
    };
};

Is implemented like this:

struct my_concrete_struct {
    void(*foo)();
};

void foo() {
    printf("this is the concrete struct");
};

void init(struct my_concrete_struct * implementation)
{
      (*implementation).foo = &foo;
}

Notice the init function, which is essentialy the constructor. In C++, this was done for us, but in plain C, we need to remember to initialize the pointer every time. Now when we run this:

int main() {
    struct my_concrete_struct implementation;
    init(&implementation);
    
    struct my_abstract_struct * interface = 
        (struct my_abstract_struct*)&implementation;
    
    (*interface).foo();

    return 0;
}

We get:

this is the concrete struct

Well that’s polymorphism if I’ve ever seen one! And we didn’t need any virtual abstract method object nonsense. This works, because C stores the struct members sequentially in memory. If your “derived” struct has the same order of member variables and the “base” is a strict subset (both of those things are important, otherwise you’ll end up with some weird behaviour), casting the pointer to the “base” gives you the ability to use it as an abstract interface. Pretty neat!


1 Martin, R. C. (2018). Clean architecture: A Craftsman’s Guide to Software Structure and Design. Pearson Professional.