Working Wandbox-ed modern C++ code, is HERE.
C++ Polymorphism with no inheritance is by no means, somewhat advanced concept. But, this is just because it is (in C++) based on different concepts v.s. some other good and popular languages where it is in the foundations of the language design.
Lessons from GO
For example in GO LANG polymorphism is definitely not tied to inheritance. And this is one very much popular and good programing language, indeed.
I have prepared “earlier” one (I hope) succinct and useful GO LANG example of interfaces, types and messages dancing together.
GO is simple and that is interactive online code. So do not be afraid and at least try it once. I have tried to comment it extensively.
GO LANG OO concepts are inheriting (pun intended) from SmallTalk. Not from C++ or Simula. GO interface,
can be seen conceptually as a collection of messages.
1 2 3 4 |
// GO LANG type Vehicle interface { Start() string } |
After the above, GO structs (types) can implement the message “Start”. They do not inherit the interface in order to implement it, as one does in C++. In GO you implement the message (aka method) that GO interface exhibits.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// GO LANG // type is not a C++ class type Automobile struct { } // Automobile implements the message // "Start() string" // which is exhibited by the // Vehicle interface func (h Automobile) Start() string { return "Automobile, is an abstract concept, and can not start." } |
There is no interface keyword in C++. Usually, C++ interface is Abstract Base Class (ABC). Admittedly (at least to me) GO LANG ABC’s are an unnecessarily complex subject and we better quickly jump back to standard C++ we know and love (well at least some of us).
Slight Detour
Subtyping is actually what C++ developers think of as polymorphism. Common base class as a parent to subtypes implementing the same interface of the said common base.
Parametric Polymorphism is what C++ developers call “generic programming”. In “typeless” languages (think JavaScript) that kind of polymorphism is actually transparent. It just happens. (some are calling this ‘delegation’ but I do not think that is the right name to use in this context)
1 2 3 4 5 6 7 8 9 10 |
// Java Script is language // based on "Parametric Polymorphism" // made possible by lack of types var use_the_object_to_alert = function ( some_object ) { // any object will do // which implements the // alert method aka "message" some_object.alert("Hi!") ; } |
some_object
above is not any kind of base class. It is “just” an object. In JavaScript, an object is implemented as a collection (dynamic array) of functions.
[nextpage title=”The standard C++ way”]
By now, I think, we have (more than ) enough inspiration and pointers to the actual Architecture and Implementation of the modern C++ solution.
It might be the best course of action to imagine a code using a non-existent solution and then implement it. Let us use the “Modern Factory” we have actually (shamefully) implemented with the help of inheritance and a native pointer to its base.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// type tags to actual engines // visible to the assembly line enum class engine_tag : char { old = 'O', next = 'N' }; namespace engine_workshop { // hidden from clients // interface to swappable engines // Abstract Base Class struct IEngine { virtual engine_tag start() = 0; }; // switchable engines struct old : public IEngine { virtual engine_tag start() override { return engine_tag::old; } }; struct next : public IEngine { virtual engine_tag start() override { return engine_tag::next; } }; } // inner |
The engine inside the Automobile is a simple native pointer, private to the Automobile class “facade”.
class Automobile {
mutable IEngine * the_engine_ {} ;
// ... the rest goes here
Nice simple and usable. But, the wrong concept.
I do not like inheritance especially not in the production code. It delivers code that is sneakily becoming more and more difficult to change and maintain. If you are curious about why there are “few” more reasons. For which to understand please do use YouTube or whatever you prefer.
Just make it a template
We need no abstract base classes, pointers and inheritance. C++ has all we need to avoid that.
The concept is: Engine is a concrete class which “has” or “implements” some particular method (the message).
Back to our Modern Car Factory, we see, rather unsurprisingly, to use the engine, we need a start()
method.
1 2 3 4 5 6 7 8 9 |
// this is the engine struct engine { // and this is the method // it implements bool start () { /* some implementation */ return true ; } } ; |
Next, we make an Automobile into the template and give it an engine parameter.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// template <typename EngineType > struct Automobile { // no pointer to interface here mutable EngineType engine_{} ; // Automobile delegates the start // implementation // to it's engine bool start () const { return engine_.start() ; } // the rest goes here |
Nice, simple and usable? Stop here. Before anybody gets lost, we need to know…
Where are we now?
Instead of a pointer to a polymorphic root (aka base) we have “just” a template. This is where we are, and this is where I want us to be.
1 2 3 4 5 |
// before class Automobile final { Engine * engine_ {} ; // the rest .... } ; // Automobile |
We have replaced the pointer with the template argument.
1 2 3 4 5 6 |
// after template <typename ET> class Automobile final { ET engine_ ; // the rest ... } ; // Automobile |
The key: we have no inheritance hierarchy (of Engines).
Beside using templates, I have seen other extremely complex solutions to avoid inheritance.
Here, I am simply saying: Focus on the message you need to pass to the object. Implement the abstraction that has the messages, not the interface offspring that implements the interface with all the messages required now and coming or not in the future. To do this one can use templates. Simple. But.
Every template instantiation delivers a new type. C++ is a statically typed language. That is: compiled types are made by compile-time template instantiations. Therefore.
Can we not compose types at runtime?
Thus we can not code a factory method, in the templates scenario, that assembles Automobiles at runtime.
1 2 3 |
auto my_new_car = car_factory::make ( car::factory::engine_type::petrol ) ; |
Why not? To implement the above. in the architecture I have just described, we would need to have statically built aka “pre-built” model (c++ type) for each combination customer might request. We would need to have an internal catalogue (a map maybe) matching each possible combination, of instances of existing types.
Ok then, let us imagine, our factory is allowing customers to (seemingly) mix and match “bases of the cars” with engines and wheels. Our internal catalogue that is now required might be similar to this standard c++ std::map:
1 2 3 4 5 6 7 8 9 |
// // declaraion and definition // map< string, automobile > catalog { "model A", petrol_engine_aloy_wheels, "model B", hybrid_engine_kevlar_wheels // and so on for every possible combination } ; |
The first problem above is one very static system, declared, defined and closed at compile time. For each new future combination, we would need to add code and recompile our car factory software system.
The second problem is we have no common type “automobile” as we have no inheritance. Thus we can not declare the internal catalogue aka c++ map.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
struct IAutomobile { // pure virtual virtual bool start () = 0 ; }; // inheritance again template<typename EngineType> struct Automobile : public IAutomobile { EngineType engine_ {} ; override bool start () { return engine_.start() ; } } ; |
That would be the only way to have an internal catalogue of all possible types.
1 2 3 4 5 |
// // map value is interface // no can do // map< string, IAutomobile> catalog_of_all_types ; |
Just a reminder: in standard C++, Abstract types cannot be used as parameter types, as function return types, or as the type of an explicit conversion.
Here I do realize there is no point of pushing this template based paradigm further. I would need (again) an ABC to be able to “herd” all the current and future Automobile types.
Where do we go from here? What gives?[nextpage title=”Start again, think modern C++”] I am by now, obviously, a car factory architect, and also, I might be some modern C++ user with more or fewer skills at that. Let me start with what I know.
I “need” to have a car factory that will allow mix-and-match service. Customers will “say” what they want to mix-and-match and the factory will actually need to mix-and-match parts to deliver the required combination.
Mix? Match?
What immediately springs to mind is a “mix-in pattern”. A beast among the patterns. A make or break for modern programming languages. Something that made “experts” to reconsider JavaScript as a language to appreciate.
Like this (rather good but complex) text about “Many talents of JavaScript“. Same concept but appropriate language. Not C++. I already mentioned JavaScript on the top of this article. So, was that useful? Yes, at least to confirm we are on the right conceptual path.
So let us (again) code in modern C++ these parts we will need to mix-in to compose the automobiles.
From now on we are in the namespacecar_factory
, so we will omit this from the code. Let’s start with the “spare parts”:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
enum class engine_tags : char { legacy = 'L', contemporary = 'C', hybrid = 'H' }; enum class wheel_tags : char { alloy = 'A', chrome_vanadium = 'C', steel = 'S' }; |
Our factory will let customers choose wheels and engines. By using the above tags customers will communicate what they want. The key point: At runtime.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// no 'auto' does not mean // 'automobile' here ;) auto diesel_car = car_assembly_line( engines::diesel{}, wheels::alloy{} ); auto petrol_car = car_assembly_line( engines::petrol{}, wheels::chrome_vanadium{} ) ; |
We have just got two different automobile models straight and brand new from the assembly line. From the same factory. Without an internal catalogue of all possible models, pre-made and ready.
Next, we want to use the models made into automobiles.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// start the diesel car auto has_started = diesel_car.start(); // what is the factory tag // of our car? auto model_tag = diesel_car.tag(); // how many wheel knobs // we have on our new car? auto how_many_wheel_nobs = diesel_car.knobs()); |
I am sure you can appreciate this is a POC code. Thus it might look rather not real, but it actually has to work as a real conceptual base for the real car factory software solution.
Back to work. Let us prepare our engines and wheels we know we will need.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
// remember we car_factory is the top level namespace namespace wheels { // wheels struct alloy final { constexpr wheel_tags uid() const { return wheel_tags::alloy; } constexpr int fix_points() const { return 3; } }; struct chrome_vanadium final { constexpr wheel_tags uid() const { return wheel_tags::chrome_vanadium; } constexpr int fix_points() const { return 6; } }; struct steel final { constexpr wheel_tags uid() const { return wheel_tags::steel; } constexpr int fix_points() const { return 4; } }; } // wheels namespace engines{ // engines struct diesel final { constexpr engine_tags uid() const { return engine_tags::legacy; } constexpr bool start() const { return true; } }; struct petrol final { constexpr engine_tags uid() const { return engine_tags::contemporary; } constexpr bool start() const { return true; } }; struct hybrid final { constexpr engine_tags uid() const { return engine_tags::hybrid; } constexpr bool start() const { return true; } }; } // engines |
Please ignore the modern C++ paraphernalia here, like,constexpr
final
, etc. This POC will work without them. They are here just to make code later on, more malleable to not-a-poc coding of production quality, for you.[nextpage title=”Car Front”] Now the “hard part”. Thinking time :) Consider this simple sentence: You sit in your car and you start it.
The Car Interface
How do you “start the car” as a person/driver/user who is not a car assembly expert? By using the “interface” to the car. What you see, sitting in the car, is just the “interface”. The keyhole, where you slot in your keys and start the car. Which activity, in turn, starts the engine that happens to be part of the car.
I do not want to use the term “interface” here, it is heavily overloaded, I will use the term “front”.
1 2 3 4 5 6 |
// // "Car shassey" in good old // simple days struct car_front final { }; |
We need to have this base part of the car. Oops, yet another overloaded term: “base”. Rather: The core component into which we will add the parts to assemble the car. The core component of the model that the customer requested. That key part will act as the “front” to the finished car. Remember no “interface” inheritance here. Just polymorphism. Only C++ methods as messages.
Now our car_front
needs to understand the message “start”. And it needs to know what to do in order to serve the driver.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// "Car shassey" in good old // simple days struct car_front final { // the start message specification: // message name is 'start' // message returns 'true' or 'false' // upon receiving an execution request // car_front delegates // the request to the engine template<typename T> constexpr bool start( // must receive the engine // as the message argument const T & engine_ ) const { // engine spec: must understand the message // 'start', and must return 'true' or 'false' return engine_.start(); } }; // car_front |
Now the car front has the ability to understand the “start()” message. Good. But why this “engine type” argument? This is so that message is generic. This generic start() message will make the car_front
in itself generic. Without making the car_front
a template. Which we have shown before we do not want.
The Concept
Thus the concept is: the car_front
is a collection of activities the car from our factory can do. With messages (methods), it understands so that the driver can request any of those activities. By driver passing the messages to it. In C++ terminology by executing the methods on this.struct
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
struct car_front final { template<typename T> constexpr bool start (const T & engine_) const { // ask the engine given // to start return engine_.start(); } template<typename W> constexpr int knobs_required (const W & wheel_) const { // ask the wheels // return the result return wheel_.fix_points(); } template<typename W, typename E> constexpr const char * type_tag (const W & wheel_, const E & engine_ ) const { // make the car tag // from engine and whells tag static char car_tag[]{ (char)engine_.uid(), '-', (char)wheel_.uid(), (char)0 }; return car_tag; } }; |
Perhaps the car_front can be called a “courier”. (no, not a dispatcher, yet another overloaded term). An entity that has the vocabulary of messages it knows how to delegate. To the subordinates it fronts. Sometimes we recognize this as a “proxy” pattern.
Factory at last
Now many of you who have persevered bravely this far might be confused. Thus, let me thus quickly deliver the core code that makes the car assembly line.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
// // 'lambda mixer' (tm) by dbj.org // inline auto car_assembly_line = []( auto engine_, auto wheels_ ) { // obtain the types to // be used *with* the front using ET = decltype (engine_); using WT = decltype (wheels_); // the final abstraction we // are actually assembling in here // c++ private class struct finished_car_ final { // different types // of parts ET engine{}; WT wheels{}; // always the same car front // aka 'core component' car_front front{}; // the messages every car // from this factory // will understand auto start() { return front.start(engine); } auto tag () { return front.type_tag(wheels, engine); } auto knobs() { return 4 * front.knobs_required(wheels); } operator std::string() { return this->tag(); } }; return finished_car_{}; }; |
Here, I have implemented the “factory method” as a generic “lambda mixer”. This allows me to deliver the runtime functionality where the customer can decide (yes at runtime) what model she wants and to receive not-a-template private (aka inner) class representing the finished car.
And this is our “holy grail”: a polymorphic solution, with no inheritance. All the cars from my factory have the same methods. But they do not inherit from the common base. No inheritance whatsoever.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
auto diesel_car = car_assembly_line( engines::diesel{}, wheels::alloy{} ); auto petrol_car = car_assembly_line( engines::petrol{}, wheels::chrome_vanadium{} ) ; // use the diesel car auto has_started = diesel_car.start(); // what is the factory tag // of our car? auto model_tag = diesel_car.tag(); // how many wheel knobs // we have on our new car? auto how_any_wheel_nobs = diesel_car.knobs()); // use the petrol car auto has_started = petrol_car.start(); // what is the factory tag // of our car? auto model_tag = petrol_car.tag(); // how many wheel knobs // we have on our new car? auto how_any_wheel_nobs = petrol_car.knobs()); |
As they say: If you know how to drive the diesel, you know how to drive petrol :)
Summary
I have presented a solution, that I might not call a “pattern”, but rather a mechanism. As a pattern, we can describe it conceptually, but unlike a pattern, we can generate several blueprints and implementations, from this one concept.
However, the key advantage, (I have not done before in all of my C++ years) is the ability to create dynamically, at run-time, an assembly.
Combination of parts, contained in one standard struct.
That client obtains and uses it, to reach the functionality the assembly is a front of. Perhaps a concept very close to mix-in but broader in functionality and applicability. With the key advantage: no inheritance for sub-classing or for sub-typing. No smart pointers either.
To distinguish it from some other “mix-in” implementations, I am calling it “Lambda Mixer”(TM). Maybe in a subconscious attempt to draw a parallel with a skilled cocktail master? Who knows.
This is one long multi-page post already. In a very near future, I shall formalize this mechanism and will discuss it’s flexibility, applied to some other use cases.
Working Wandbox-ed modern C++ code is HERE.