Regarding the problem you mentioned, I've often tried to break my problem domain in "abstract concepts" using templates, static assertions, code generation etc. I don't think I have a real word success story. Abstraction doesn't work out of course for business logic because generalizing over the mess which our world is is just incredibly hard, but also not for algorithms, because quite frankly, efficient implementation is the result from learning a thousand neat little tricks and applying them in the right situations. Abstraction remove these opportunities.
For example, recently I've seen a Java application with a parameterized container class. The parameters were abstract and thus the container had to use tons of nested Hashmaps to get from A to B. Had it admitted that the only sensible parameter was "int", everything could have been implemented using a few super-fast arrays.
Another problem is that abstraction actually grows cancerours dependencies across the code base. If we instead design clean data interfaces reusing basic data types and add a few lines of comments / constraints / consistency checks, this dependency hell just freezes down and we notice that abstraction wasn't the actual problem to begin with.
For the basic quick and dirty app, or script, of course it's extremely important to have generic STL containers and a std:: algorithm, or python dicts / set / iterators, and be done with it. Also in rare cases, abstractions like Monoid, Functor, Applicative, Traversable, Monad can come in handy when you can reuse a few lines of code from a generic library.
But to come back to your point, I think that's all largely explored territory and the point of diminishing returns has long been surpassed.
Thoughts? Counterexamples?
The impression may be biased by my own frustrations dealing with bad abstractions (like: 90% of the classes I've seen out there are semantically broken, a.k.a not state machines).
(To back that up, there is a python script of mine which I think is rather clean. It has basically a single class in it, which was needed because the API of cmd.Cmd relies on inheritance. I had to spend quite some time to circumvent that insane amount of brokenness. I've put in a few cynic comments out of frustration. If you're interested: https://github.com/jstimpfle/gitadmin/blob/08a2c0b7dd1a8950e... Good times.)
Name the use cases where you think inheritance is a good fit.
Where inheritance shines is in enabling close coupling across module boundaries. An example is event routing: how do you enable customization of how events flow through your app? UIKit handles this through inheritance. The base method gives you the default behavior, which you can override to customize. You can stop propagation by just not calling super, or redirect the event by calling the method somewhere else, etc. It's flexible.
JavaScript handles this clumsily, by trying to anticipate all the customizations you might want to do and providing APIs for each of them (event.preventDefault, event.stopPropagation...) Despite all that, frameworks like React have to reimplement event dispatch, because the default behavior is too inflexible.
Here inheritance is the glue that enabled close coupling between UIKit's default event dispatch algorithm and outside modifications to it.
Classname_method(&structure, ...)
Inheritance is a good fit when you want execution efficiency when implementing interfaces. It makes you think in terms of base behavior vs customization and thereby re-use code properly. Most of the time, the resul is that the base class implementation is generic enough to do what you need regardless of the specific instance. But if you really need to override base behavior, that's when you can have things layered on top of basic inheritance.For example:
* Database access (ORM or NoSql)
* Any sort of server object access (eg File, Request, Socket)
* APIs that discourage duck typing to increase cohesion
* Low level implementations that need efficient implementations of function calls
* Automatic, zero-overhead memory management http://www.stroustrup.com/resource-model.pdf
Maybe compared to duck typing inheritance increases cohesion. But classes in practice most often discourage cohesion. That's why they came up with Aspect-oriented programming. Not that that is practical, but it comes from the insight that organisation as stateful objects naturally leads to coupling, since you must lump most functionality into one mostly closed concept. And the odds are your next class will have a very similar structure regarding dependencies and implementation. And then you end up with inheritance just to share code, and end up with these totally meaningless ball-of-mud inheritance structures.
> Low level implementations that need efficient implementations of function calls
I'm pretty sure the most efficient "implementation" of function calls are still function calls.
I'm not an OOP expert, but I get the impression that type classes are similar to interfaces in Java and C#, which are used all the time in those languages. If you told a C# programmer that IEnumerable was an example of bad architecture, I think you may get a somewhat different response.
My feeling is this is a classic example of an area where the perfect is the enemy of the good.
At one extreme we have C++'s current design -- try substituting the types into the template and see what explodes. At the other extreme (which is what C++0x templates did) is that if a template passes 'concept check', we know, 100%(ish), that the resulting code is going to compile.
The problem is, that this means when you write the concepts for a function, you have to have concepts for every corner of the C++ language you could make use of. This means for many functions the concept list ended up being longer than the function! Further, it is easy to miss optimisations (such as rvalue references), because you didn't 'concept' for them.
The latest aim is (to me) a good middle-ground. It checks (for example) that something is "numberish", or "iteratorish". You can easily create freakish types which will pass concept check, and then not compile, leading to the kind of error messages users currently get. But the common cases, of people passing sensible, but wrong, things into methods, will get much nicer errors.