Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
roth_stephan_clean_c20_sustainable_software_development_patt.pdf
Скачиваний:
29
Добавлен:
27.03.2023
Размер:
7.26 Mб
Скачать

Chapter 5 Advanced Concepts of Modern C++

Concepts: Requirements for Template Arguments

The C++ template mechanism is a Turing Complete metalanguage for generic programming, which calculates types and values at compile time. There is nothing comparable in other programming languages, which only come close to the power of C++ templates.

On the downside, data type independent (generic) programming with templates is inherently complex and demanding. Just take a look at an outstanding example, the template code of the C++ Standard Library, and you know what I mean. You will be confronted with code that in many ways does not conform to the clean code guidelines I’ve presented in this book. On the contrary, it looks complex and cumbersome.

Many developers who write domain-specific application code are often very intensive users of template libraries, but they rarely come into a situation to write a template class or template function. But even as a user of templates, you often get into a situation where you have instantiated a template with one or more concrete data types for its template arguments, and were confronted with a very long and verbose list of cryptic error messages.

Just an example: earlier in this chapter, in the section about the <algorithm> header, I presented a small code example (see Listing 5-25) where a std::vector<T> filled with strings was sorted and then printed on stdout. In Listing 5-33, I modify this example a little bit by using a std::list<T> instead of a std::vector<T>.

Listing 5-33.  Using a std::list Instead of a std::vector for names

#include <algorithm> #include <iostream>

#include <string>

#include <string_view>

#include <list> // formerly: <vector>

void printCommaSeparated(std::string_view text) { std::cout << text << ", ";

}

int main() {

std::list<std::string> names = { "Peter", "Harry", "Julia", "Marc", "Antonio", "Glenn" };

215

Chapter 5 Advanced Concepts of Modern C++

std::sort(begin(names), end(names)); std::for_each(begin(names), end(names), printCommaSeparated); return 0;

}

If you now compile this example, the compiler confronts you with a long list of sometimes hard-to-understand error messages. Then you are faced with the question: What the heck went wrong? I only exchanged the container type; can’t a std::list<T> be sorted?

The reason for that bunch of errors is that a std::list<T> only offers a bidirectional iterator; that is, an iterator that can be used to access the sequence of elements in both directions. However, the algorithm std::sort requires a random access iterator, i.e. an iterator that can be used to access elements at an arbitrary offset position relative to the element it points to.

The basic problem is that a template instantiation is first of all only an obtuse, textual replacement of the template arguments by concrete types. The compiler can only determine whether the template is at all suitable to work correctly with this type when it compiles the instantiated template. In addition, it is almost impossible to implement a function or class template in such a way that it fits every conceivable concrete data type.

With the new C++20 standard, template designers get a long-awaited feature: Concepts! Concepts are named sets of semantic requirements or constraints that can be applied on template parameters and are evaluated at compile-time. Thus, they become part of the template’s interface. We also get improved error messages, because the compiler can check if the requirements specified in a concept are satisfied by the concrete template arguments.

A C++ concept can be specified completely by yourself (I’ve done this in some code examples in this chapter before), but there is also a collection of predefined core concepts in the <concepts> header. These can be combined to build ­higher-­ level concepts. Furthermore, several concepts are also defined in other headers of the Standard Library, such as in <iterator> and <ranges>.

216

Chapter 5 Advanced Concepts of Modern C++

Specifying aConcept

Let’s assume that we want to develop a function template named function() whose one and only template parameter must be copyable. The corresponding C++ concept looks like this:

#include <concepts>

template<typename T> concept Copyable =

std::copy_constructible<T> && std::movable <T> && std::assignable_from<T&, const T&> && &&

std::assignable_from<T&, const T&> && std::assignable_from<T&, const T>;

Note  The previous code snippet is for demonstration purposes only. It is not necessary to define a concept like Copyable by yourself, because it is included in the <concept>: std::copyable<T> header.

The specified requirement that a template parameter T should be copyable corresponds to a logical AND of five core concepts from the <concepts> header. Our new concept also got a good, semantic name: Copyable.

Another way to specify a concept is using the requires expression:

template<typename T>

concept Addable = requires (T x) { x + x; };

In this case, we have specified that a concrete type for template argument T can be added.

Applying aConcept

Now we apply the concept Copyable<T> by specifying the requirements for the template parameter T of a function, as shown in Listing 5-34.

217

Chapter 5 Advanced Concepts of Modern C++

Listing 5-34.  Using a C++20 Concept to Specify Requirements That T Must Satisfy

class CopyableType { };

class NonCopyableType { public:

NonCopyableType() = default; NonCopyableType(const NonCopyableType&) = delete;

NonCopyableType& operator=(const NonCopyableType&) = delete;

};

template<typename T>

void function(T& t) requires Copyable<T> { // ...

};

int main() { CopyableType a; function(a); // OK! NonCopyableType b;

function(b); // Compiler error! return 0;

}

Because I deleted the copy constructor and copy assignment operator of the NonCopyableType class, we get the following expressive error message (excerpt; compiler: Clang 13.0.0):

prog.cc:28:3: error: no matching function for call to 'function' function(b); // Compiler error!

^~~~~~~~

prog.cc:20:6: note: candidate template ignored: constraints not satisfied [with T = NonCopyableType]

void function(T& t) requires Copyable<T> {

^

prog.cc:20:30: note: because 'NonCopyableType' does not satisfy 'Copyable' void function(T& t) requires Copyable<T> {

^

[...]

218

Chapter 5 Advanced Concepts of Modern C++

I highlighted the relevant line with bold font: The data type NonCopyableType does not satisfy the requirements of our concept named Copyable<T>. In the following lines of this error output (intentionally omitted here and replaced by an ellipsis: [...]), the compiler tells us which partial requirement of the concept was not satisfied. This is a significant improvement compared to those cryptic error messages from former times.

By the way, the function from Listing 5-34 can be written much more compact and elegant without the requires clause:

template<Copyable T> void function(T& t) {

// ...

};

Or even better, using the C++20 abbreviated function template syntax:

void function(Copyable auto& t) { // ...

};

Templates, concepts, and metaprogramming during compile time are extremely powerful features of modern C++ whose primary target group is clearly library developers. They justify a much more detailed introduction. Unfortunately, a deep dive into these language constructs is far beyond the scope of this book.

219

CHAPTER 6

Modularization

“I have absolutely no idea about space exploration. I’m a software guy. But because I’m a non-expert, I’ve been able to bring the software concept of modularity into the space sector, which was never done before.”

—Naveen Jain, software engineer, entrepreneur and founder, May 12, 2015

This quote is from a blog article [Jain15] by Naveen Jain, one of the three founders of the Florida-based private company Moon Express Inc., which was founded in 2010. The business objective of Moon Express (MoonEx) is to mine natural resources of economic value, such as ore, on the moon. For this purpose, MoonEx engineers designed a family of flexible and scalable robotic explorers based on modular spacecraft architecture. The foundation for their modular architecture is NASA’s Modular Common Spacecraft Bus (MCSB), which is a general-purpose spacecraft platform that can be configured as landers or orbiters. The MCSB not only reduces costs; NASA states that an uncrewed space mission that is built on the MCSB platform is roughly one-tenth the price of a conventional mission. Furthermore, by using a modular platform, NASA will no longer “reinvent the wheel,” by being able to reuse many components.

Since the early days of software development, developers strove for well-­modularized software. The reason for this is obvious: Once a piece of software has reached a certain size, it gets more and more difficult for humans to grasp it in its entirety. We do not modularize for the computer. A computer doesn’t need a modularized version of the code to run it. It’s our own cognitive limitations that force us to break down a software system in smaller pieces.

In addition, people expect further positive effects from well modularized software: reusable modules, better maintainability, and easier extensibility. Creating a scalable, configurable, and flexible product family like MoonEx did with its robotic space probes is the goal. Furthermore, modules with minimal interdependencies and well-designed interfaces are easier to test.

221

© Stephan Roth 2021

S. Roth, Clean C++20, https://doi.org/10.1007/978-1-4842-5949-8_6