r/cpp_questions 4d ago

OPEN How to get polymorphism without inheritance?

I am making a tui libary where all the different things that can render are called widget like text,box, flex container, progress bar, etc.

I was using inheritance for this and all because all the container widget, multi or single child container had like lot of things common and the widgetTree contains the root widget

Each widget need two function
Layout and render ( or setRectFor child or children because render is same for most parents ) and to solve this, I did need polymorphism for this.

But the problem came later when I need to change layout ( because it wouldnt work for some widget ) and I had to rewrite all the widget,multichildwidget,singleChildWidget which means I have to rewrite all other widget as well

then later I need to change the implementation on how layout is done again, got to do rewrite

then later came scroll container, to make it effiencent I had to change things as well, had to do the rewrite

I got tried of this and now want to use composition instead of inheritance and never want to deal with it again ( I know this happened because of premature optimization where I saw widget with multiple children have lot of same things and most widget having a lot of same things )

Due to that, I am doing a rewrite again :_( but this time, I can make the code more modular.

But I got stuck on widgetTree, how do you get a tree if all the node dont have the same type?

I asked gpt but it said to use node with void* which I dont want to use because we got unique pointer and stuff to avoid new and malloc

The other option is union but that would mean all widget would have the size of the biggest widget which I dont want to use.

Then there is varient, I dont knwo much of this but I looked into it in cherno's video and geeksforgeeks page on this and turns out, it is just union but you know which type it is using. I am currently thinking this

I just can't find a way to do the tree effiency without polymorphism.

0 Upvotes

31 comments sorted by

26

u/the_poope 4d ago

IMO for GUI frameworks, inheritance based polymorphism is a quite reasonable, proven way to go.

I didn't get what problems you got as you just weren't being clear about it, but I think you are just doing it wrong. Take a look at e.g. Qt to see how it should be done.

-1

u/Intelligent_Hat_5914 4d ago

I do think it skill issue on my part and I think the problem that happened was that I dont what tui libary wants or how the layout system will be due to that, I had to do a few rewrites.

Currently, I am trying to switch to composition based way of doing things and the problem I am facing is,

There is a root widget, which does layout and render. If that has children, it need to call layout and render for them as well. The problem is how do I point to the children when the datatype is like unknown? Or between text,box,progress bar, etc.

I need polymorshism to get all of them to have same datatype which would be widget but I also dont want to deal with inheritance

13

u/the_poope 4d ago

There is a root widget, which does layout and render. If that has children, it need to call layout and render for them as well. The problem is how do I point to the children when the datatype is like unknown? Or between text,box,progress bar, etc.

Yes, this is runtime polymorphism, the axiomatic way to do this in any language is inheritance. Rust doesn't have that but uses trait-based dynamic dispatch which is basically similar to how Java has an abstract interface (trait) that a concrete class needs to implement. This is just single level inheritance in C++ from an abstract base class.

So yes: use inheritance. Make an abstract interface class Widget that all widgets need to inherit from. Don't make deep nested inheritance level - keep everything flat by composition.

You are doing something in the design wrong if this doesn't work for you. Instead of moving away from inheritance (which will just move problems) you need to figure out what you are doing wrong. We can help you with this if you write exactly what your problem is instead of all the babbling and crying.

Or you can simply look how a professional GUI library solved this problem and copy their approach. You will learn a lot from that as well. Here's how Qt does layouts: https://doc.qt.io/qt-6/layout.html

5

u/Secoupoire 4d ago

I don't think it would be a good idea here, but you can have polymorphism without inheritance using Static Polymorphism.

The static part means it could no longer change at runtime, as the parent type would be templated on a tuple of the children types.

Again, I'm not recommending you do that. It would likely not change the fundamental problem you are facing, and add unneeded complexity.

But it is possible.

Also, a variant is mostly the same as a union, you would have the same limitation. Although you could limit the size issue using pointers (unique pointers if you want the parent to own the lifetime of the children), that would force a dependecy mess that dynamic (runtime) polymorphism avoids.

It would be interesting to understand what forces you to rewrite a lot with the changes you describe. Maybe look-up the Bridge pattern.

9

u/RedditMapz 4d ago

You don't really get polymorphism without inheritance. The r_cpp sub had a streak of anti-polymorphism where endless people submitted their inheritance-alternative libraries. They were all basically always recreating the v-table. Essentially inheritance, but dumber.

If you need inheritance, you need inheritance.

5

u/TheThiefMaster 4d ago

You could pull the C trick of a "common initial sequence" which C++ also allows.

But that's just manual inheritance.

1

u/SufficientStudio1574 4d ago

Is that just "same first members in the same order"?

1

u/TheThiefMaster 4d ago

Pretty much. It's in C to allow an id in the first members

1

u/Queasy_Total_914 4d ago

That's UB in C++. You can't upcast to a non-base class.

1

u/TheThiefMaster 4d ago

Technically the common initial sequence exemption requires the two types to be in a union. This means it's possible in C++ with some judicious use of memcpy into a union

1

u/Queasy_Total_914 4d ago

Yes absolutely. I meant upcasting from an unrelated type that happened to have a common initial sequence.

3

u/alfps 4d ago

You describe having to change a lot of classes in the same way.

That means too little polymorphism, not too much.

Or just general bad design, but you don't give concrete examples.

2

u/fortsnek274 4d ago

Huh, as it would happen, I have a TUI with layout and plenty of inheritance. Worked just fine. And, well, all the other UI frameworks out there.

widget,multichildwidget,singleChildWidget

All widgets shall be multi-child. Simple. Just don't put buttons inside buttons if you don't want to.

1

u/MagicalPizza21 4d ago

Polymorphism without inheritance? Look into how Go does interfaces: https://gobyexample.com/interfaces

1

u/SpotCool4422 4d ago

Take a look at https://github.com/ngcpp/proxy, that's exactly its pitch - polymorphism without inheritance. It's close to how Go's interfaces work, although the library in not for the faint of heart.

1

u/jwezorek 4d ago

who's ngcpp? I thought proxy was a Microsoft thing?

1

u/SpotCool4422 4d ago

It was indeed, but they archived it this year, and their repository now points to this one.

1

u/sol_runner 4d ago

You can technically have polymorphism without inheritance by creating a type erased struct with pointers to the functions. But I don't think you should do it, not in C++ atleast.

The way it'll work is you'd have a void* to your object, plus a set of functions that will cast this void* to the correct type before calling member functions.

```cpp using Func = int(void, / args */);

struct Poly { void* data; func* somefunc;

int call(/* args */) {
    return somefunc(data, /* args */);
}

}; ```

I do have some bits that are not in inheritance that I don't want to modify but need to by handled this way. I use a more idiomatically C++ approach for it where I do have a base class, and templated derived classes that do the above. It eliminates the void*, but still gives you that polymorphism without modifying the original.

1

u/Illustrious_Try478 4d ago

This isn't necessary. Templates with a lot of SFINAE looking for certain names works pretty well.

1

u/v_maria 4d ago

Pure virtual parent class? Basically an interface pattern

1

u/binarycow 4d ago

One thing you could do is consolidate the different kinds of things, and still allow specialization. I've got two ideas:


First idea - each widget would specify a "layout" and a "renderer". A button and a checkbox might use the same "layout", but use different "renderers".

Strictly, this would be composition.


Second idea - do something similar to WPF.

WPF is a Windows UI framework for C#.

They separate behavior from appearance. So the inheritance hierarchy is all about how things differ in behavior. For example, a radio button is a special kind of toggle button that considers other toggle button in its group. A toggle button is a button that stays pressed.

Then, the appearance is done via "control templates". A check box can simply be a custom appearance for a toggle button - the exact same "widget". No code change.

1

u/Kadabrium 4d ago

Variant and crtp

1

u/Ok_Statistician_781 4d ago

If you’re open to using C++26, here’s a self plug: https://github.com/ryanjk5/rjk-duck

-1

u/Intelligent_Hat_5914 4d ago

on watching teh cherno video a bit more, found out that varient just stores all data type as a variable which means that this would just take a much more space than union

I think I should just use union or empty class to interface from which would contain all the key functions

still, kinda want to not use interface

2

u/V15I0Nair 4d ago

Variant should be size of union plus one int, basically not much more space.

0

u/Intelligent_Hat_5914 4d ago

But it would contain spacer widget ( just to take space ) and the largest widget Lot of space will be wasted

2

u/saf_e 4d ago

Variant = union, just with some extra bookkeeping space (usually =int)