what is the best way to cast n C++? I am doing it C style (type)(object)
This is where it gets academic though. When you are using objects from 'classes' the types themselves hold their "cast-ability".
The largest and most common is the "is-a" relationship. If the "is-a" test holds, you don't need to cast it. "is-a" tends to work 'peer and parent' direction.
"If a ByteStream is-a Stream", then you can treat a reference to "ByteStream" as "Stream", but NOT vice versa.
So, the most likely place to put a cast is in a 'specialised path'. If 90% of the code works on "Stream", but 10% needs to know it is infact a byte stream, say to implement (WordAlign()) .. You will see blocks which compare the "Stream reference" by it's underlying type, if it "is a" it will get cast.
pseudo:
IF streamRef is-a ByteStream:
ByteStream bStreamRef = (cast ByteStream)streamRef
bStreamRef->WordAlign()
However. This is considered an "anti-pattern". If you add another type of stream you need to update the cast selectors. These "selectors" will not stay in one place. They will spread... everywhere. If every bit of code that needs to know it's a XStream and not a YStream starts doing the above, it makes it impossible to change the Stream hierarchy later. If you look at something like the history of the Java language and runtime libs, you will find this mistake repeated and entire hierarchies end up marked as "Deprecated".... left to rot.
That is where the design patterns come in. The above has many different solutions, all of them have their own costs to. Right up to things like the "Visitor pattern" which is honestly a beast, especially when people implement it with "inline anonymous members".
But... in my opinion. If you have a small known number of types, a fairly obvious and fairly static hierarchy. A simple "is-a" selector is fine.
A pattern that I personally prefer is the "As a" pattern. If you have a class which is of TypeX, but could be of sub TypeY, rather than burden the caller with determining the relationship, you move that into the class itself. The syntax depends on a few things, even within C++ though. If you have "overloaded return types" you can just do:
TypeY tY = typeXInstance.getAs();
If you don't have overloaded return types, you have to pass an example:
TypeY tY = typeXInstance.getAs(TypeY.class);
annoying in older versions of Java and still some APIs today you have to do:
TypeY tY = typeXInstance.getAs(new TypeY()); // and burn memory churn unless the JIC optimised it.
The point is and its sort of "half" the visitor pattern without too much overhead, just the caveat that it will couple your hierarchy internally which itself can bite you later if the complexity scales.
Couple = "Change this" and you need to "Change that"
In MCU land, your types and structures will be fairly limited in size and complexity anyway. It is unlikely, assuming the hypotethical Stream hierarchy, you will probably only deal with "CharStream", "ByteStream", maybe, "BufferedStream". The number of locations it will matter are probably fixed.
The visitor pattern and that level of abstract become more "fitting" if you are looking at the "Object hierarchy" UI widget in something like Autodesk Fusion. Each individual object in the drawing is there, there are dozens, maybe hundreds of types of "thing" in that tree, different icon, different label, different right click context. That would make one very large "is a" selector. And when you add a new "View" or "Gadget" as a feature, a nightmare to update. There going to the length of a visitor pattern makes sense. The visitor pattern is sort of like a "double callback" so the caller does not need to know the type of the callee... in advance. The leaf types provide a method to get their Visitor instance. The caller then calls the generic method on the Visitor instance. That then calls the actual leaf node contextually. So the "contract" is met without both ends having to know what each other was. A bit like "Double NAT"'ing a OneToMany VPN link.
C++ specifically has a "streams" component built into the runtime, so you would be better starting there before implementing a "Streams" tree. Although, as an academic OOP vehicle to learning, it is fairly close to the MCU layer. While being OOP.
Streams is all that:
cout << "Hello" << (2,3) << "World"
stuff. Ill be honest and say most of my C++ in the real world stayed well away from these. All our string routines where C-like direct memory manipulation, including inline ASM for "tokenizing".