Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

C required the simplest form of OOP, but who standardizes it decided to deny the feature to the language: the ability to have callable structure methods (function pointers) with explicit "self" pointer. Not even constructors/destructors. After all it's C, and you can write list->init() and list->free(). This simple form to bind data and the functions operating on such data would make many codebases better.


I made a pre-processor [1] to add similar features to C, and after reading your comment I’m thinking that it would be simple to add a setting/#pragma to do these transformations:

    list->init(); → list->init(list);
    list.init();  → list.init(&list);
[1] https://sentido-labs.com/en/library/cedro/202106171400/#back...

I normally avoid the function pointer overhead, which can be done with _Generic:

    #define append(VEC, START, END) _Generic((VEC), \
      Vec_float*: append_Vec_float, \
      Vec_str*: append_Vec_str, \
      Vec_cstr*: append_Vec_cstr \
      )(VEC, START, END)
https://sentido-labs.com/en/library/cedro/202106171400/#loop...

But list->init(list) might be a simpler solution for most cases, and compatible with C89/C99.


I’ve built it for now in a separate branch called “self”:

    git clone -b self https://github.com/Sentido-Labs/cedro.git
    cd cedro
    make bin/cedro # Just “make” will build cedrocc etc.
    bin/cedro - <<'    EOF' # Mind the indentation.
    #pragma Cedro 1.0 self
    list->init();
    list.init();
    list->append(123);
    list.append(123);
    EOF
The “self” flag after “#pragma Cedro 1.0” activates the “self” macro, because it should not be done by default.

Result:

    list->init(list);
    list.init(&list);
    list->append(list, 123);
    list.append(&list, 123);
I’ll try it out for a few days and if it works well in practice I’ll document it and merge it into master.


> I made a pre-processor

You followed the path of C++.


> You followed the path of C++.

To some extent yes, I know about cfront.

This is just another iteration on that old idea.


Sure; you’re basically implementing The prehistoric version of C++, called “C with Classes.” If you’re unaware, see Stroustrup’s Design and Evolution of C++ book.

Which is fine! Should be interesting.


That wouldn't actually help much. In the case that there is no inheritance, using a function pointer has unnecessary overhead (pointer storage, indirect call, additional parameter). In the case of inheritance, the pointer type differs and thus the function pointer type wouldn't be compatible either.


You need to use a void* self pointer. And indirect through the vtable in all calls to virtual methods, even those that are part of the object itself. Thus, e.g. a method defined on a base class might end up calling an implementation deep in the inheritance hierarchy, that didn't even exist when the base class code was written. There's no real equivalent to this when doing simple interface inheritance, but it's very much part of the actual semantics here.


Inheritance is nowadays almost considered a bad practice, in fact newer languages, such as Rust, doesn't support it, and in languages that has it (Java, C#, etc) nowadays they always tell you to prefer composition over inheritance.

> using a function pointer has unnecessary overhead

This is true, it's inefficient. But for a lot of application, also irrelevant at performance level, and would provide a good abstraction.


Inheritance is the best solution to some problems.

It got a bad reputation because it was abused. Bad practice is using inheritance when a tuple or a map would suffice.


To some problems that are almost always seen in the academic world but I've yet to see in my daily job.

Inheritance (from implementation, I've nothing against implementation of interfaces or inheritance from abstract base classes) has a ton of problems, more importantly the fact that it makes the code more difficult to understand and to evolve.

Composition on the other hand is something more natural, even if we think about real life: you don't usually take an object and "extend" it, you take multiple object and use them together to build something!


It's strange that you seem to be an absolutist on this topic. What is wrong if inheritance is an elegant solution to a relatively small number of problems? Historical overuse of inheritance doesn't take away from that.


What problem is left? UI? React is showing composition wins there too.


It's just a code reuse mechanism. In some cases you can do with it what you might also do with callbacks, or yes, composition. Or in some cases inheritance might be handy.

I don't know why we need to be so judgmental about it.

I think inheritance is especially good if you have an interface (in the OO sense, like some languages use an "interface" keyword for), but you have some common or default methods, which a specific implementation may or may not override, or maybe there is some boiler plate or tedium where the most common implementation might belong in a base class. I think this is handy for something like a device driver.


  > has a ton of problems
this may be a good reference with examples (and some good dose of nuance) on the subject:

https://blog.gougousis.net/oop-inheritance-what-when-and-why...


The main issue with inheritance is hard coupling of data and code. As code evolve from data modeling point of view it can be advantages to split the state managed by one object into several data structures. With data inheritance such refactoring is almost impossible. When inheritance is limited only to pure interfaces the access to the state is not hard-coded and can be redirected as necessary.


AIUI you can implement inheritance as a pattern on top of existential types, using a self-recursive definition trick. Not sure if Rust has full existential types yet, but they're definitely a goal since they're needed e.g. for better async support.


Rust has a flavor of inheritance through Traits and #derive.


No, traits are not inheritance, nor is derive that is default implementation of some traits.

Traits are like interfaces in OOP languages (simplified), the equivalent of a class would be a struct + all the impls of that struct. And that thing cannot be extends, a struct is fixed when defined and nobody can extend it, you can use composition that is the correct to me way to go.


That's why I said "flavor". It's not exactly inheritance obviously, but provides some of the same functionality. There are many ways to skin an OO cat.

Traits describe the methods implemented, and you can override with specific implementations or inherit the default, from a level up.


Traits implement interface inheritance, yes. #derive has nothing to do with derived classes in OOP, it's an annotation-like facility that adds auto-generated code to any object.


Isn't #derive a syntactical sugar for a bare impl?


> prefer composition over inheritance

Right... There is an animal in a dog.


Last time I checked, C++ used function pointers behind the scenes to achieve virtual method dispatch.


Typically the object contains a single pointer to the vtable which is then the list of function pointers.

You can do this structure in C, but more common is the struct of function pointers which avoids a level of indirection at the cost of object size.


But how do you tackle creation of objects, call malloc everytime?

And how do you for example, create an array of objects, like one can easily do in c++?




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: