It is too long, but can be informative.
Generics in Java is a type-erasing mechanism and automatic generation of type-conversion and type-checking code.
template in C ++ - mechanisms for generating code and templates.
You can use the C ++ template to do what Java generics do with little effort. std::function< A(B) > behaves in a covariant / contravariant manner with respect to types A and B and conversion to other std::function< X(Y) > .
But the primary design of the two is not the same.
The Java List<X> will be a List<Object> with some thin packaging on it so that users do not have to do type casting when retrieving. If you pass it as List<? extends Bar> List<? extends Bar> , it will again get a List<Object> essentially, it just has some additional type information that changes the way throws work and what methods can be called. This means that you can retrieve items from the List in the Bar and know that it works (and checks it). For the whole List<? extends Bar> List<? extends Bar> only one method is created.
A C ++ std::vector<X> is essentially not std::vector<Object> or std::vector<void*> or anything else. Each C ++ template instance is an unrelated type (except for pattern matching). In fact, std::vector<bool> uses a completely different implementation than any other std::vector (now this is considered a mistake, since differences in the implementation of the "leak" are annoying in this case). Each method and function is generated independently for the specific type that you pass to it.
In Java, it is assumed that all objects will fit into some hierarchy. In C ++, this is sometimes useful, but it has been discovered that it often does not fit the problem well.
A C ++ container should not inherit from a common interface. A std::list<int> and std::vector<int> are unrelated types, but you can act on them evenly - they are both sequential containers.
The question "is an argument to a sequential container" is a good question. This allows anyone to implement a serial container, and such serial containers can have such high performance as manual C code with completely different implementations.
If you created the common root std::container<T> , which you inherited from all containers, it will either be populated with the t222 table or useless except for the tag type. As a tag type, it will introduce itself into all containers without std , requiring them to inherit from std::container<T> in order to be a real container.
Instead, the feature approach means that there are specifications as to what a container is (sequential, associative, etc.). You can check these specifications at compile time and / or allow types to note that they can claim certain axioms through some traits.
The C ++ 03/11 standard library does this with iterators. std::iterator_traits<T> is a feature class that provides iterator information about an arbitrary type T Someone completely unrelated to the standard library can write their own iterator and use std::iterator<...> to automatically work with std::iterator_traits , add their own type aliases manually, or specialize std::iterator_traits for passing necessary information.
C ++ 11 goes further. for( auto&& x : y ) can work with things written long before a range-based iteration was developed without touching the class itself. You simply write the free begin and end function in the namespace, to which the class belongs, which returns a valid forward iterator (note: even invalid front iterators that work close enough), and for ( auto&& x : y ) suddenly starts working.
std::function< A(B) > is an example of using these methods together with type erasure. It has a constructor that accepts everything that can be copied, destroyed, called using (B) and whose return type can be converted to A The types that he can accept may not be completely related β only what is required is required.
Due to the design of std::function , we can have lambda invokables, which are unrelated types, which can be erased in the regular std::function if necessary, but when the type is not deleted, their invokation action is known from this type. Thus, the template function, which accepts the lambda, knows at the point of involution what will happen, which simplifies the local operation.
This technique is not new - it was in C ++ with std::sort , a high-level algorithm that is faster than C qsort because of the ease of embedding called objects that passed as comparators.
In short, if you need a regular runtime type, enter erase. If you need certain properties, check these properties, do not force a common base. If you need certain axioms for storage (unchecked properties), either a document or require callers to request these properties through tags or attribute classes (see How the standard library handles iterator categories - again, not inheritance). When in doubt, use free ADL-enabled functions to access the properties of your arguments, and your free functions use SFINAE by default to find a method and call if one exists, and do not work otherwise.
Such a mechanism eliminates the primary responsibility for a common base class, allows you to adapt existing classes without changes to convey your requirements (if reasonable), erases the type of place only where necessary, avoids virtual overhead and ideally generates clear errors when properties not found.
If your ENGINE has certain properties, it must pass, write a feature class that checks them.
If there are properties that cannot be tested, create tags that describe such properties. Use the specialization of a feature class or canonical typedefs so that the class describes which axioms are stored for the type. (See Iterator Tags).
If you have a type of type ENGINE_BASE , do not require it, but instead use it as an assistant for the specified tags and traits and axiom thixists, for example std::iterator<...> (you never need to inherit it, it just acts in as an assistant).
Avoid specifying requirements. If usually_important never called on your Worker<X> , then perhaps your X does not need B in this context. But check the properties in a certain sense than "the method does not compile."
And sometimes, just a punt. After this practice, you can make something harder for you - so it's easier. Most code is written and discarded. Know when your code will be saved, and write it better and more stretchable and more restrained. Know that you need to practice these methods in one-time code so that you can write correctly when you need to.