Search code examples
c++listinheritancevectorstd

C++ Why do std::vector<> and std::list<> not share a common base class/interface?


Since im mainly working on C#. After quite some time a had to work on a C++ project and was wondering why C++ isn't relying on inheritance or interfaces for related methods such as std::vector<>::begin(T, Allocator) and std::list<T, Allocator>::begin()? In C# this is commonly used for classes with a similar behaviour such as the ICollection<T> interface on List<T> and ReadOnlyCollection<T>.


Solution

  • C++ actually supports interfaces in multiple ways

    1. by virtual functions. This is classical style nominal subtyping and that corresponds to the C# approach you quote

      C++ does support interfaces in the classical sense where an interface is class-like with abstract functions. In c++ this would be pure virtual functions. This equivalent to what other languages offer. However this comes with a performance penalty as calling virtual functions has a performance hit as compared to regular functions (which moreover can also be inlined).

      Besides the performance penalty, the disadvantage is that it is not possible to add interfaces to existing types. (This applies to all languages with this feature not just C++)

    2. by concepts1. This is the more modern interface approach based on structural subtyping.

      std::vector or std::list do not have a virtual function based interface because of performance reasons. Nevertheless they follow naming and conceptual conventions. Both have iterators (of different class though, but iterators) and have common function such as begin end insert, push_back and so on. There is even std::begin which can operate on all containers.

      Structural subtyping for interfaces is also used in the Go language. It offers much more flexibility and extensibility. For instance one can add interfaces to existing types without having to change to existing type.

      With C++20 concepts1 it is now also possible to explicitly specify the requirements on the types (i.e. what methods they must support, or more generally which expressions that operate on them must be valid). This defines interfaces (or concepts how they are called in C++). But this is decided not by the type implementer, but rather by the usage of the type.

    So bottom line: They do not share a common (virtual) base class due to performance reasons. But they do share a common interface in the sense of structural subtyping (via C++ concepts).

    Notes

    1. On a technical level the C++11 std::enable_if provides the same functionality. C++20 concepts are much cleaner, though and more "interface" like in the modern sense.

    Addendum: An example which demonstrates that inheritance (where nominal subtyping is closely related to) is really limiting and not adequate is numbers. Assume we have 'complex', 'float', 'int'. According to normal rules 'complex' should be the base class and all others inherit from it. For a compiled language this would result int a big performance hit already. When one has fixed bit width, it becomes even more complicated. int32 would be a subtype to int64, and to float64, but not to float32 and so on. Together with arbitrary length integers this becomes completely messy.

    A structural type system is then the much better approach, where compatibility rules are decided on properties of the types rather than inheritance. Same applies to interfaces.