Blog 4 min read

C++20 Concepts: Eliminating Generics Paradigm Drawbacks

Share this article
C++20 Concepts: Eliminating Generics Paradigm Drawbacks

As Bjarne Stroustrup points out, “C++ is a multi-paradigm language.” It supports many different programming styles, or paradigms, and object-oriented programming is only one of them. Some of the others are structured programming and generic programming. Over the years, C++ experts such as Andrei Alexandrescu, Scott Meyers, and Herb Sutter have promoted the use of generic programming, often referred to as Modern C++ Design.

Here’s what Andrei Alexandrescu says about Modern C++ Design:

Modern C++ Design defines and systematically uses generic components - highly flexible design artifacts that are mixable and matchable to obtain rich behaviors with a small, orthogonal body of code.

Three aspects of his point of view are particularly interesting:

  • Modern C++ Design defines and systematically uses generic components.
  • Highly flexible design.
  • Obtain rich behaviors with a small, orthogonal body of code.

On the other hand, OOP is very popular: inheritance and RTTI are two powerful mechanisms to design a C++ application, and many developers prefer this paradigm over the generic programming approach.

Here's a common definition of inheritance:

In object-oriented programming (OOP), inheritance is when an object or class is based on another object (prototypal inheritance) or class (class-based inheritance), using the same implementation (inheriting from an object or class) specifying implementation to maintain the same behavior (realizing an interface; inheriting behavior). It is a mechanism for code reuse and to allow independent extensions of the original software via public classes and interfaces.

Many C++ experts recommend avoiding the overuse of inheritance and dynamic polymorphism. After all, what’s wrong with inheritance?

Short answerVery high coupling.

Let’s take the implementation of a tax-calculation class as an example.

generics1

The CTaxCalculator collaborates only with classes inheriting from ICalculator and can’t use any other non-ICalculator class, even if it could help calculate the tax. The calculator implementation class will be highly coupled with ICalculator, with no way to use another kind of class unless we introduce additional classes and interfaces to work around this limitation. For example, the adapter pattern is a solution for working around the tight coupling caused by inheritance. Think about it: some GoF design patterns exist specifically to address issues caused by the tight coupling introduced by inheritance.

Generic programming to the rescue

With generic programming, the same tax calculator could be implemented like this:

generics2

The CGenericTaxCalculator class, on the other hand, calculates the tax by using any type capable of calculating the tax, without needing to know its concrete type. What matters are the methods implemented by the type, not the specific class it belongs to. That’s what makes generic programming more natural and flexible. Indeed, in the first implementation it’s like in the real world: a company looking for a developer accepts only graduates from a specific school and rejects all the others even if they have the needed skills. But this flexibility comes at a price: the code can become harder to understand. In OOP, I can simply go to the definition of ICalculator to see what is expected of this type. However, with the generic programming approach, it can be difficult to know exactly what is expected of the template parameter: which members must it contain? Which constraints must be satisfied?

C++20 concepts to the rescue

Here's a short description of the C++20 concepts:

Concepts are an extension to C++'s templates, published as an ISO Technical Specification ISO/IEC TS 19217:2015.[1] They are named boolean predicates on template parameters, evaluated at compile time. A concept may be associated with a template (class template, function template, or member function of a class template), in which case it serves as a constraint: it limits the set of arguments that are accepted as template parameters.

The concepts feature was postponed several times. The good news is that C++20 will include this interesting feature.

We can find in this interesting document the motivation behind the concepts feature:

The intent of concepts is to model semantic categories (Number, Range, RegularFunction) rather than syntactic restrictions (HasPlus, Array). According to ISO C++ core guideline T.20, "The ability to specify a meaningful semantics is a defining characteristic of a true concept, as opposed to a syntactic constraint."

With concepts, we can address the constraint-specification issue to make the code more readable and maintainable. It’s true that Boost has provided a concepts implementation for many years, but adding them to the language will make C++ more powerful and unique.

Another well-known drawback of generic programming is its error messages. Sometimes we can’t easily understand why an error is reported by the compiler, and we can waste time trying to figure out exactly why the code is not working as expected.

Fortunately, the concepts feature will also improve the error messages, as explained here.

Summary

If inheritance is overused when choosing the OOP approach, it introduces high coupling between classes and forces you to add more classes just to resolve this issue — which is not the case when you choose to adopt the generic programming approach. And fortunately, the missing piece to implement readable, maintainable, low-coupled code will be part of the language soon.

Share this article