Olson CloudWorks 🚀

Why is the STL so heavily based on templates instead of inheritance

September 19, 2026

📂 Categories: C++
Why is the STL so heavily based on templates instead of inheritance

The Standard Template Library (STL) is a cornerstone of modern C++ programming, providing a rich set of data structures and algorithms. A common question arises when learning about the STL: Why is the STL so heavily based on templates instead of inheritance? This design choice wasn’t arbitrary; it reflects a conscious decision to prioritize performance, type safety, and flexibility. Inheritance, while powerful, introduces complexities that templates elegantly sidestep in the context of generic programming. Understanding this fundamental design principle unlocks a deeper appreciation for the STL’s capabilities and its impact on C++ development. The use of templates allows for compile-time polymorphism, resulting in highly optimized code tailored to specific data types, a crucial aspect for high-performance applications. We’ll explore the reasons for this decision, examining the benefits templates offer over inheritance in terms of performance, code bloat, and type safety, illustrating these points with examples and references to authoritative sources.

Performance Considerations: Compile-Time vs. Runtime Polymorphism

One of the primary reasons the STL favors templates is performance. Templates enable compile-time polymorphism, also known as static polymorphism. This means that the specific code to be executed is determined during compilation. This contrasts sharply with inheritance, which typically relies on runtime polymorphism (dynamic polymorphism). Runtime polymorphism involves virtual functions and a vtable lookup, adding overhead to each function call. This overhead, while sometimes negligible, can become significant in performance-critical sections of code, especially within heavily used algorithms like those found in the STL. As Bjarne Stroustrup noted in “The C++ Programming Language,” the goal was to create a library that was both generic and as efficient as hand-coded solutions [Stroustrup, 2000].

Consider a simple example: sorting a vector of integers. With templates, the sorting algorithm is specialized for integers at compile time. The compiler knows exactly which functions to call and can optimize the code accordingly. With inheritance, the sorting algorithm would likely operate on pointers to a base class, requiring virtual function calls to compare elements. These virtual calls introduce overhead that templates avoid. The performance gains from using templates are particularly noticeable when working with primitive data types or small objects, where the overhead of virtual function calls can be a significant fraction of the total execution time. The decision to use templates reflects a commitment to providing a high-performance library for C++ developers.

Furthermore, templates allow for aggressive inlining by the compiler. When the compiler knows the exact type of the data being manipulated, it can often inline function calls, eliminating the overhead of function call setup and teardown. This is much harder to achieve with inheritance, as the actual type of the object being pointed to is not known until runtime. This capability provides significant performance benefits in many scenarios within the STL.

Avoiding Code Bloat: The Template Instantiation Model

A potential downside of templates is code bloat. Each time a template is used with a new type, the compiler generates a new version of the code (template instantiation). However, the STL’s design minimizes this issue. While code bloat is a legitimate concern, the STL designers prioritized performance and type safety, accepting the potential for increased code size as a trade-off. Modern compilers are also adept at optimizing template code, reducing the impact of code bloat. The alternative, using inheritance with virtual functions, could lead to more complex code and potentially larger object sizes due to the inclusion of vtables in each object.

One way the STL mitigates code bloat is by carefully designing the template parameters. For example, many STL algorithms operate on iterators rather than directly on containers. This allows the same algorithm to be used with different container types without generating separate code for each container. Additionally, techniques like expression templates can be used to further optimize template code and reduce code bloat, as described in “C++ Template Metaprogramming” by David Abrahams and Aleksey Gurtovoy [Abrahams & Gurtovoy, 2004]. The internal link explains the benefits of templates in more detail.

Consider the example of a std::vector. When you create a std::vector, the compiler generates a specialized version of the std::vector class for integers. If you then create a std::vector, another specialized version is generated. While this does lead to code duplication, it allows the compiler to optimize each version for its specific data type. The alternative, using a base class with virtual functions, would require all elements to be stored as pointers to the base class, introducing overhead and potentially reducing type safety.

Type Safety and Compile-Time Error Detection

Templates provide superior type safety compared to inheritance-based approaches. With templates, type checking is performed at compile time. This means that any type errors will be caught during compilation, preventing runtime surprises. Inheritance, especially when used with void pointers or type casting, can lead to runtime errors that are difficult to debug. Compile-time type checking is a crucial feature for writing robust and maintainable code, especially in large projects. The STL’s reliance on templates ensures that type errors are caught early in the development process, reducing the risk of runtime bugs.

For instance, if you try to insert a string into a std::vector, the compiler will generate an error during compilation. This is because the compiler knows the exact type of elements that the vector is supposed to hold. With inheritance, if you used a base class that accepted any type of object, you might not discover the type error until runtime, when you try to access the string as an integer. This early detection of type errors is a significant advantage of using templates.

Furthermore, templates enable the use of static assertions (static_assert) to enforce type constraints at compile time. This allows developers to specify requirements on template parameters, ensuring that the code is only used with types that meet those requirements. This level of type safety is difficult to achieve with inheritance-based approaches. The strong type checking of templates contributes to the overall reliability and maintainability of C++ code.

Flexibility and Generality: Beyond Basic Polymorphism

Templates offer a level of flexibility and generality that is difficult to achieve with inheritance alone. Templates allow you to write code that works with a wide range of types, even types that were not known at the time the template was written. This is because templates are parameterized by types, allowing you to customize the code for specific data types without modifying the original template. Inheritance, on the other hand, typically requires a fixed hierarchy of classes, limiting its flexibility. As Scott Meyers explains in “Effective C++,” templates provide a powerful mechanism for generic programming that is well-suited to the STL’s needs [Meyers, 2005].

Consider the std::sort algorithm. This algorithm can be used to sort any container that provides iterators, regardless of the type of elements stored in the container. This is possible because the std::sort algorithm is a template that is parameterized by the iterator type and the element type. With inheritance, you would need to create a separate sorting algorithm for each type of container and element, which would be much less flexible. Templates allow for a more generic and reusable approach to algorithm design.

Templates also enable the use of concepts (in C++20 and later), which allow you to specify requirements on template parameters in a more formal and expressive way. Concepts provide a way to constrain the types that can be used with a template, ensuring that the code is only used with types that meet certain requirements. This further enhances the type safety and flexibility of templates. The combination of genericity and type safety offered by templates makes them an ideal choice for the STL.

  • Templates enable compile-time polymorphism, leading to significant performance gains.
  • Templates provide superior type safety, catching errors during compilation.
  • Templates offer flexibility and generality, allowing code to work with a wide range of types.
  1. Define the template with type parameters.
  2. Instantiate the template with specific types.
  3. The compiler generates specialized code for each type.
  4. The resulting code is highly optimized for the given types.
Infographic showing performance comparison between templates and inheritance
FAQ ---
**What is the main advantage of using templates in the STL?**
The main advantage is performance due to compile-time polymorphism, avoiding the overhead of virtual function calls.
**Does using templates always lead to code bloat?**
While templates can lead to code bloat, the STL's design and modern compiler optimizations minimize this issue.
**How do templates improve type safety in C++?**
Templates allow for compile-time type checking, catching type errors during compilation rather than at runtime.
In summary, the STL's heavy reliance on templates is a deliberate design choice driven by the need for high performance, strong type safety, and maximum flexibility. While inheritance has its place in object-oriented programming, templates provide a superior mechanism for generic programming in the context of the STL. The compile-time polymorphism, type safety, and generality of templates make them an ideal choice for creating a reusable and efficient library of data structures and algorithms.
  • Consider profiling your code to identify performance bottlenecks.
  • Experiment with different template parameters to optimize for specific data types.
  • Use static assertions to enforce type constraints and improve code safety.

By understanding the reasons behind the STL’s design, you can better leverage its power and write more efficient and robust C++ code. Now that you understand why templates are preferred, consider exploring related topics such as template metaprogramming or the use of concepts in C++20. By diving deeper into these areas, you can further enhance your understanding of generic programming and the STL.

Question & Answer :
I mean, aside from its name the Standard Template Library (which evolved into the C++ standard library).

C++ initially introduce OOP concepts into C. That is: you could tell what a specific entity could and couldn’t do (regardless of how it does it) based on its class and class hierarchy. Some compositions of abilities are more difficult to describe in this manner due to the complexities of multiple inheritance, and the fact that C++ supports interface-only inheritance in a somewhat clumsy way (compared to java, etc), but it’s there (and could be improved).

And then templates came into play, along with the STL. The STL seems to take the classical OOP concepts and flush them down the drain, using templates instead.

There should be a distinction between cases when templates are used to generalize types where the types themselves are irrelevant for the operation of the template (containers, for examples). Having a vector<int> makes perfect sense.

However, in many other cases (iterators and algorithms), templated types are supposed to follow a “concept” (Input Iterator, Forward Iterator, etc…) where the actual details of the concept are defined entirely by the implementation of the template function/class, and not by the class of the type used with the template, which is a somewhat anti-usage of OOP.

For example, you can tell the function:

void MyFunc(ForwardIterator<...> *I); 

To be clear, ForwardIterator is OK to be templated itself to allow any ForwardIterator type. The contrary is having ForwardIterator as a concept.

expects a Forward Iterator only by looking at its definition, where you’d need either to look at the implementation or the documentation for:

template <typename Type> void MyFunc(Type *I); 

Two claims I can make in favor of using templates:

  1. Compiled code can be made more efficient, by recompiling the template for each used type, instead of using dynamic dispatch (mostly via vtables).
  2. And the fact that templates can be used with native types.

However, I am looking for a more profound reason for abandoning classic OOP in favor of templating for the STL?

The short answer is “because C++ has moved on”. Yes, back in the late 70’s, Stroustrup intended to create an upgraded C with OOP capabilities, but that is a long time ago. By the time the language was standardized in 1998, it was no longer an OOP language. It was a multi-paradigm language. It certainly had some support for OOP code, but it also had a turing-complete template language overlaid, it allowed compile-time metaprogramming, and people had discovered generic programming. Suddenly, OOP just didn’t seem all that important. Not when we can write simpler, more concise and more efficient code by using techniques available through templates and generic programming.

OOP is not the holy grail. It’s a cute idea, and it was quite an improvement over procedural languages back in the 70’s when it was invented. But it’s honestly not all it’s cracked up to be. In many cases it is clumsy and verbose and it doesn’t really promote reusable code or modularity.

That is why the C++ community is today far more interested in generic programming, and why everyone is finally starting to realize that functional programming is quite clever as well. OOP on its own just isn’t a pretty sight.

Try drawing a dependency graph of a hypothetical “OOP-ified” STL. How many classes would have to know about each other? There would be a lot of dependencies. Would you be able to include just the vector header, without also getting iterator or even iostream pulled in? The STL makes this easy. A vector knows about the iterator type it defines, and that’s all. The STL algorithms know nothing. They don’t even need to include an iterator header, even though they all accept iterators as parameters. Which is more modular then?

The STL may not follow the rules of OOP as Java defines it, but doesn’t it achieve the goals of OOP? Doesn’t it achieve reusability, low coupling, modularity and encapsulation?

And doesn’t it achieve these goals better than an OOP-ified version would?

As for why the STL was adopted into the language, several things happened that led to the STL.

First, templates were added to C++. They were added for much the same reason that generics were added to .NET. It seemed a good idea to be able to write stuff like “containers of a type T” without throwing away type safety. Of course, the implementation they settled on was quite a lot more complex and powerful.

Then people discovered that the template mechanism they had added was even more powerful than expected. And someone started experimenting with using templates to write a more generic library. One inspired by functional programming, and one which used all the new capabilities of C++.

He presented it to the C++ language committee, who took quite a while to grow used to it because it looked so strange and different, but ultimately realized that it worked better than the traditional OOP equivalents they’d have to include otherwise. So they made a few adjustments to it, and adopted it into the standard library.

It wasn’t an ideological choice, it wasn’t a political choice of “do we want to be OOP or not”, but a very pragmatic one. They evaluated the library, and saw that it worked very well.

In any case, both of the reasons you mention for favoring the STL are absolutely essential.

The C++ standard library has to be efficient. If it is less efficient than, say, the equivalent hand-rolled C code, then people would not use it. That would lower productivity, increase the likelihood of bugs, and overall just be a bad idea.

And the STL has to work with primitive types, because primitive types are all you have in C, and they’re a major part of both languages. If the STL did not work with native arrays, it would be useless.

Your question has a strong assumption that OOP is “best”. I’m curious to hear why. You ask why they “abandoned classical OOP”. I’m wondering why they should have stuck with it. Which advantages would it have had?