DЕV.ВG Community Day 2025

Удобството на програмирането чрез последователности и изгледи в модерния C++ / Петър Армянов

52:15 · 21 Jun 2025 · YouTube

About this talk

This talk by Petar Armyanov focuses on modern C++ features, specifically sequences and views introduced in C++20 and later. He explains how these new tools simplify programming by replacing traditional iterator logic with more intuitive and elegant syntaxes. Throughout the session, Petar illustrates how sequences allow for more straightforward handling of data, reducing complexity when using standard algorithms. He emphasizes the advantages of views for creating efficient pipelines, enabling operations like transformations and filters without the overhead of creating new containers. The speaker also addresses potential pitfalls and performance considerations when adopting these modern language features, providing practical advice for developers to navigate the complexities of C++.

Full transcript

Hello. Just a second. Yes. Great. Ah, today I'm going to talk to you about some things in more modern C++. Ease of use and programming with new things like sequences and views. Ah, but before that, a few words about me. I am Petar Armyanov and I have been working with C++ for many years, more than 20, and I also teach at Sofia University. I work for different

companies. I'm curious about what's happening in the language. I am interested in learning new things and teaching them to people who are interested. So I hope today I can also get you excited about some new developments in the language. And I'll show you an example from the past, a few things from the new language, and we'll end with more good examples. I hope I have time for

them and for questions. Well, let's start with a very simple example. I have one vector. Everyone knows what this is. I want to take the first five elements, but we start with the first five even elements, but we start somewhere further back than the third position. We used to have nice old tools like this hoe from the Roman era, and we could write such wonderful code that

was relatively understandable. This is not even the most ancient possible option. Now we can do the same thing in a much more elegant way, with a much more understandable syntax, a syntax that we also know from other languages, with so- called compehhntions. But how do we get to this point? Yes, by the way, this is a much more modern hoe that is almost safe, it is slightly

turned downwards, so it is a little harder to step on, but we will see. Sequences, also known as ranges or rangees, are actually an idea that was intended as a convenient replacement for our well-known work with iterators. something that has been typical of C++ for maybe 30 years and more: instead of having two iterators, beg and End, to use for some actions on sequences of data, we

can use a single object that collects them together. Just as a couple. A very simple idea, but in C++ things can't be simple. There must be many complexities and many twists and turns to come. The goal we want to achieve is exactly that, to use our well-known algorithms more easily. We used to do such classic things. And standard algorithms, such as sorting, provided the beginning and end

of some sequence, either by using collection methods or the standard begin and end functions. Now we can do exactly the same thing, just feeding in the data, which thanks to the idea of ​​these sequences, notice the separate a not ace, the separate namespace , they recognize the type and say yes I can get the beginning and the end somehow. All of this is defined in this very

Ranges space, which was newly added separately from all existing ones, as well as the corresponding ranges header file, which includes a lot of things. includes new algorithms, versions of existing ones. Also many auxiliary functions that help us work with them. They adapt some types, saving us from having to write complex template arguments, new introduced types to handle and use. Already in C++20 we have concepts that define

some rules. There are many new concepts written here for this part. special categories like views, pipeline stream expressions, we saw the example, spans or scopes, which are also a type of view and a type of sequence, and all of this is wrapped up in a few namespaces and a few header files. Mainly the ranges header file and ST Ranges, the namespace. A wonderful set of tools to

step into and work with, it depends on how one approaches them. Be careful. So, specifically for ranges, this is actually, as we said, a simple concept that includes a beginning and an end. Nothing complicated. And it is implemented by very well-known types of ours, such as standard containers. All standard containers have begin and end methods. So we know, they allow us to do a for reach loop.

Classic arrays can also be used in this way when we know the type in advance, of course, but we have added a number of new classes to expand the possibilities and potentially provide more flexibility and convenience. And all of this requires different types of sequences, different categories to use, similar to our well-known interpolators. And for example, they are described in concepts so that we have type compatibility

while using our things in some way. Many of these things are well known from iterators. For example, an input range, the idea of ​​one-way reading, for some data, we have an output range, which allows writing, of course, forward range, by directional range, random access range. All the things well known about iterators have been carried over to but there are also many new categories with specific actions. a

take range, which implies a continuous sequence of elements in memory, just like arrays or character strings, a size range, something that can tell us its size in a constant amount of time. Things we also know from containers, but are not applicable to iterators per se We also have concepts describing categories relative to the relationship of our sequence to the container it operates on. For example, Borange, which

says that aa uses let's say the container on loan, is not related to its lifecycle in any way. There are many more, viewable range, common range, and so on. Something important that makes a difference between sequences that are in C++ and containers is that in containers the beginning and the end, the iterators to the beginning and to the end, are always of the same type. This is

important for containers, while for sequences it is not mandatory. Accordingly, they have a special aa type common range, which guarantees the concept of common range, which guarantees that the beginning and end are the same type and allows you to convert to such a type when you need to use familiar old algorithms. That's great, but C++tics are everywhere. What to look for, where to be careful, what not

to do. Wonderful tools. First, be very careful when working with constant sequences, constant ranges, because constancy affects their behavior differently, depending on what container they are built on. We will see examples. And it's important to keep in mind that they contain similar things as pointers. That is, you can have constant pointers, pointers to constants. We will come back to this several times. They have different behavior. Be

careful with this. And if they are constant, they may not support iteration, that is, even though you have a sequence, you have a beginning, you have an end, you cannot move from one end to the other, like a constant pointer. However, they may allow data to be changed, which is surprising, but the constancy is not transferred from the type to the data to which it is bound.

again like a constant pointer, they do not support concurrency because they are non-trivial types that have their own context and they are not designed to be used in a multi-threaded concurrency environment. They have no protection from this thing. So be careful with this thing too. like iterators, they can have undefined behavior. These things may be highlighted because they may not be. It depends on the specifics. exceptions

as much as you want. Aa can have undefined behavior when you change the container they are built on. Like iterators, including in situations where iterators would remain valid, based on them may have undefined behavior, becoming invalid. And like iterators, they can be left hanging, pointing to non-existent data, which is also a serious problem, like Again, be careful with conclusions. Be careful when setting a constant, it doesn't

always guarantee what you expect and can have unpleasant effects when using a constant to define a specific predefined function in some template specializations, for example. These things change, by the way, from C++ 20 to C++ 23. A controversial point in the standard is that the committee has been discussing this topic for a long time. There are still some unclear details. move towards development, towards improvement. Second, aim

to use them immediately, similar to pointers. Do not transfer them to places where you may have a loss of the object on which they are defined. So, in addition to these sequences, there are a few very nice new and convenient things. Nothing so complicated so far, right? Nothing new. What's the good new thing? One is a subsequence. Subranges. They allow you to describe a subsequence based on

an entire container, described by a beginning and some end. This is one option. This marker or sentinel or end is something we are well familiar with from our standard strings. In some way, we may recognize the element as the end of our sequence. Like any array for sequences, we can do things like this. Second, we may have a beginning and a number. Again we think of good

old arrays or strings. pointer to the beginning, number of elements. Great. And all of this can happen with standard arrays. That is, we can create a subsequence on an array and have it extract its corresponding parameters from it, if possible at compile time Markers can be of several types. There are two standards that are important. One is a default, which is used to return an iterator, to

return a type at the end of some inspection, and with it the comparison always says an error. That is, it is never reached, but it is a valid end and your container is supposed to recognize, for example by number, where the sequence ends. When you have sequences based on a start and count, the end iterator effect is sent. And the other is another interesting type of unrechable

sentinel. Both are never reached, but default assumes there is an end to your container, while unrechable assumes an infinite sequence. A sequence that has no end and will not be reached by itself. important subtle difference, that's why I've pointed them out here. That's right. You can write your own tags. This is one of the great conveniences. You may want to have special logic to put in and

have it be dynamic in some way. Here is a simple example. You can write your own array type that only needs to redefine an equality operator on an element. This post in this case is the element you are checking, that is, an iterator to the element you are checking. We're checking it here for some value, and I can put this to great use by creating a subrange

object on some set with element seven. It's important to have a type. That is, here the type must be fixed. You can also use structures that keep, for example, the end criterion in some way in context. You only expect to have an iterator comparison with your type. That's right. Great. Another nice thing is the ability to work with temporary collections. Something new. Why? Let's imagine a simple

function that generates data and returns it to us as a result. Great. Here we have aa function that generates and returns a temporary object vector of ints. There was no way we could use something like this before because we had to sell the beginning and end of our container. There's no way to do it directly from the function result. We need to assign it to a variable

and we're already materializing it. It is not a temporary existence. But now we can do this wonderful thing. We get the wonderful result of the function. We feed it to some find algorithm and it returns an iterator. What does our iterator point to? This beautiful piece of land just waiting to be stepped on. Of course, it strikes us again that our mutika is turned down, it is

not dangerous. We can achieve it, but it's a little more difficult because C++ 20 introduces the ability to borrow a collection. It recognizes from our algorithm. Here we would recognize that this is a temporary Airvue object and in fact the result would be creating an iterator over time of a collection that is currently being used by someone for MMH . A special type of Bal iterator, which

protects us as long as it recognizes its data as cataloging data, a special type that if we try to use a star or arrow operator, we will be sprayed during compilation. That is, we avoid runtime errors thanks to strong typing and the ability to use some concepts. Also more good stuff in C++20. So the hoes are there, but they're a little bit turned pointy side down. Great.

However, C++ also has its downsides. I can't do something like that. There's no way I can do a variant where I have a function that uses an iterator, for example, outputs the value or something else that happens within the same expression as ours, our function that returns data, which as we know extends the life of our collection, and actually this would be valid so far, but because

of the idea of ​​denling this thing won't compile. That is, if our process function used the type, it would recognize the result as a borol iterator, and attempting to access the data in any way, even though it is technically valid, would result in a compile-time error . So we have a little more security with compile-time checks than necessary. C++ is starting to look like itself to us

and is already limiting us from doing things that are generally valid, albeit potentially risky. And this will not be allowed. Another important part of our new library are views. They are another level of abstraction and actually represent some idea of stacking aa sequences, but they allow you to do this extremely easily. What does light mean? This means that they are designed to be easy to copy, fast,

efficient to copy, appropriate, focus on speed, they provide semantics somewhat similar to some declarators, so you can layer views on top of views, getting different properties of the data you are working on. They can be for access, that is, for read or write, very simple tasks, but they can perform transformation of the elements of the sequence, that is, change the data they operate on during use. They

can generate data. This is extremely useful in many situations to get sequences that don't actually exist. They are generated on demand with lazy evaluation a little more about them. I said again the biggest, the biggest feature, the most important feature is that their focus is on ease of use, extremely efficient and fast and a focus on speed with the ability to guarantee at compile time certain parameters,

certain properties, protection from some runtime errors thanks to strict typing and collections. Excuse concepts. They can be for reading, they can be for writing, it doesn't matter. Designed with the idea of ​​lazy computation with zy evaluation, which allows you to not receive data, not generate results that you will not use, including infinite streams. They have specific behavior that preserves some values, caches some values, some calculation results,

which is great because it gives fast performance, but as we know, cache problems are one of the most important in programming. Very often, one knows about the three types of errors in the three most common errors in programming: invalidation and off by one. We have problems with validation here. And it is important that they are defined in a separate space, namely Views, which is under the Ranges

space. For convenience, this space is aliased in the standard library as STD viws. You can very conveniently assemble such thanks to a predefined vertical bar operator Pipe and make a pipeline with sequences of actions to be executed one after the other, to be superimposed like different decorators, if you want like matryoshka dolls, and this is very convenient. Again the example we saw at the beginning. We'll see

a few more examples in a moment. Here are a few example views. We seem to imagine that we have some kind of container of data. I can make a type that is the first five elements. The nice thing about this container is that it doesn't create a new container, it depends on the data on top and just makes a view that takes the top five. This thing

is about constant time with constant memory. Note, I use the type aut everywhere, which is important in this situation because types can be extremely complex with template parameter injection. In some cases, they reach dozens. and their spelling is often very difficult, and sometimes even impossible, to the extent that with a very small difference in context you can get a completely different type. So good good situation where

people are now forced to use autotypes. Another very simple example is a function that will return to you, that is, create a filterw that will receive these five elements and from them take only the even numbers thanks to some predicate lambda function, which in this case is some other function that will calculate and filter the elements. Again, you are not filtering anything here. This is executed in

constant with constant memory. You will receive the values ​​upon request. A few other nice examples are the ability to take, for example, a specific portion of the data. Here's a simple transformation. For example, if you have a standard associative container like a map, say, you can grab just the keys with a specially defined adaptive viw. You have generators. is a well-known generator from older versions, but it

used to generate sequential numbers in some container that you feed it and it creates them. It required additional memory, ran in linear time. Here you have a generator that creates such numbers on demand in constant time with constant memory. That is, it is created in constant time, requires constant memory, and as you iterate, you can receive consecutive numbers. There are nice handy containers that divide a container,

sorry, construct, that divide a container in this case some text into subsequences with some separators. You can use it, for example, in a well-known way to separate a sequence of words, say, with a space. But here this thing could be an arbitrary subsequence. That is, I can ask to split not just on a single character, but to have a sequence of characters, and the effect will be

that I can split into longer aa with a longer separator. That's right. And a few things about view categories. Just as a summary, there are views that generate data. Data sources are several adapters like ad that enable you to views with certain concepts. I won't go into details. Yota as a generator is extremely useful. A few small simple things like single it, which gives a view with

one element or an empty view with no elements. These things are from C++20. In more recent times like C++23 you now also have a sequence of identical elements. Toasting values, repeating values ​​of the same element and more nice new views, data sources to generate them. The more interesting ones are the adaptive ones, the ones that somehow change the data they are built on, such as take, take

while, drop, drop while, those parts that take a certain set of elements. The first few or skip a few, which can be either by number of take drop or by predicate criteria. Extremely comfortable. Again, we 'll see an example in a moment. They work lazily. Alter transform different things. A few useful things are the cathors, which turns a sequence if it gives the necessary answers to the

specified concepts, fulfills the necessary requirements, as well as options for merging or splitting a sequence. For example, a join takes a sequence of sequences, a range of ranges, and disassembles one of them. Again, for constant time, it just changes the way it iterates. Great. Things look very beautiful. A few important things. The idea of views is to adapt an object, to be adapter objects that reference some

container, have the ability to operate on already existing data and provide iterators over it, and the other option is to generate this data. They are created and used lazily, meaning that that are not needed are not actually performed. An idea for quick action. They also cache some values again and can own a container. Unlike the sequences themselves, it builds sequences. The idea of views is that they

can recognize the situation in which the container is a temporary Airview and get a chance to own it, to take it in internally. Here is a simple example. A situation where we have some data. Here I have used a short name for STD views as WWS. You will see things like this very often in practice for the sake of brevity, so that they don't become too long.

And the examples are very important, when you use things with the new ranges of things in some code, don't use using name space. Don't mix them with std in any way, because aa the names are similar, very often they are the same and you have a problem with using types, extracting the template types. You may get collisions and so often sympathetic, perhaps well- known to most of

you. A message of approximately 900 lines, a message about a problem in extracting the type of template arguments, argument dection types. Therefore, never use named spaces directly. Use the full names of types and functions to avoid problems. Here we create an array, filter the elements and transform them in some way, and then display them on the screen. I've intentionally put aa captions so we can see what's

happening and we get this output, where we see that actually multiple element filterings are performed and only when we get an element that matches the filter, a transformation is performed on it. This whole thing is happening up there. These things don't happen right at the beginning when we define our type, but when we start iterating over it step by step, element by element. Extremely useful for not

overloading the system. It doesn't waste memory, it doesn't waste time during creation, but access may be inconsistent, insofar as a filter would have to find the next element on request. That is, when you request operator++ on a varet iterator you get, and it will start looking for the next element until it finds it and then it will take time, but not before you request the next element.

A few tips for quick action. First, use caches. And when this element is found, it is remembered, so that upon subsequent repeated use, say, of the star operator, extracting a value, it will not be searched for. This is one of the ideas behind caches. So, and also the construction may be more complicated. Use your types that you created. No, no, you're not repeating the same thing. Doesn't

repeat itself. Don't do things like that because it will lose its effect very quickly. These types are designed to be created once and used to the maximum extent possible, using caches. Second, think about transformations. Very often they can have the same effect. In our case, it doesn't matter in what order you arrange them both, but it is important to put, for example, the filters before the transformers,

because the filters skip elements that are not interesting and you won't take any action on them. You will save unnecessary actions. So the order can be important and significantly change the performance of your program. Even if logically the effect is the same, the result is exactly the same, the execution time may be significantly different. More tips, because there's a lot to know. copying can change behavior. So,

if I have one, like I have here, and some example in, if I later assign this V to something else, the effect might be different. And it depends even for the same code. Depending on what container or what category of iterator you are using, the behavior may be different. Very often you work, you make such constructs that get the sequence they work on or the container they

work on as a template argument to allow all sorts of possibilities. Great. The problem is that depending on the type of change, the behavior can be totally different because of the different categories of iterators you are working on. So be careful with copying because it changes the iterator and not all caches are portable. Be careful about the life of the probes, though, if it recognizes an object

that's been mixed in with something like Airvue and grabs it, it captures it inside the sequence itself, and you can very easily end up with sequences that you've saved somewhere or you've passed to some function or something and they're now referencing data that doesn't exist. If the container below is destroyed, the sequence itself has no way of understanding it and you end up with a dangling reference.

The standard introduces two types of only owning view and reference view, which you can query, but again, they don't guarantee you anything. The owning view simply takes an Airvue sequence and copies, that is, moves the data into itself, while the reference view is supposed to hold a reference alias to the data. It is in no way tied to life, but that does not guarantee your behavior. You

can easily break these things in C++. It is important not to change the elements you get in the filter and transform functions in these higher-order helper functions, because by default they are tied in some way to the implementation underneath and if you change them, you may break the caches. It is embedded in the language. The standard says so in small enough standard letters, but still be careful

of unfine behavior, that is, if you change the data in a filter or transform , it can lead to serious problems. The good thing is that here again the mouthpiece is slightly rounded. And there are concepts that say these functions should be pure functions, functions that don't change their arguments, don't have side effects, but these are so- called semantic concepts. Semati concept. which cannot be verified at

Pile Time and are a promise that you make in general terms. So, basically you can say this function implements this concept or not. The compiler trusts you, you decide whether to promise to deliver on what you promised. If you don't follow it, the undefined behavior compiler is free to do whatever it wants, including blowing up your computer. Do not store views if the underlying sequence changes. What

do I mean? The option where I have some range, I've created a view on it, I store it somewhere in some variable and while using it in some way from below I change the container, add elements, change elements, remove elements. Because of caches, you may end up with invalidated data. That is, you are referencing data that is no longer in the container. You data that does not

meet the criteria to slip through the filter, or vice versa. Ah, again it depends on the type of container. This is also something that cannot be validated by the system, it is entirely up to you to know. Avoid storing sequences if you can create them each time, but that would be expensive. On the other hand, if you change the container, they may be invalidated. Similar to iterators.

A little about the standard algorithms to use. They are also important. For both ranges and views, there are no variations of parallel algorithms, and it's very important that I can always say, okay, I have, you know, a variety that accepts an execution policy that can say I want it parallel. The compiler sees your hardware, says great, you have multiple cores. I will plant a multi-thread variety. It's

okay. It's not made for ranges. Why? Well, you say no need, I'll take my range and say point begin. I will submit it to my standard and parallel sort will not work again defined behavior because of data race no it is not synchronized access to the data in the situation in which this data is a member of the object they have a context and this context is

not a third safe it is not protected in a multithreaded environment this is a no all but not all mathematical algorithms are transferred what I mean things like for example accumulate a typical mathematical algorithm is not made for ranges and for viw, that is for Somewhere new ones have been added in the newer standard. They weren't there in C++20. Several such have already been added in 23,

such as F left. They are useful, but not all of them have versions, so sometimes we may need to submit our own view or sequence, submit it to a standard algorithm. They should work again here, as long as they are not parallel. New versions are important, so always use the range versions, not the normal versions. And when you work with sequences, with views, because aa they are

made like that with a very complex hierarchy of concepts to guarantee at compile time at least somewhat that the behavior is correct. That is, they tried their best to transfer runtime errors , errors with incorrect types to compile-time errors with the strict aa strict typing of parameters and a very complex hierarchy of concepts. That's great. Sometimes you might have a problem because these errors can this thing

can expand to errors with hundreds and hundreds of lines of descriptions, what's wrong and sometimes it's a little harder for a person to navigate. And, the correct types of the iterators you work with and the functions are guaranteed. Again, some of the concepts that are applied are semantic. For example, that the function will not change its elements. Yes, it can accept by value or const. Oh, sorry,

Constance, but that doesn't guarantee that you won't change something inside. Even the data itself is constant, through it you can have some kind of access to a side effect or access to some external memory, side effect and so on. Using such is So iterators and functions are checked against some concepts, but again a lot of those concepts are semantic. You must comply with them and it is

your responsibility. Also, the result is checked to make sure all functions return exactly what they should. You don't have any converted ones and so on. Predicates must always be Boolean. Oh, and a brief summary. If you have constant views, you may not have crawling. Again, think about constant pointers and pointers to constants. Views can be non- traversable if they are constant. Again, it can, because some allow

it. Some that don't have their own context provide an opportunity to move forward. If you have a constant reference to a view, it does not propagate downwards, meaning it allows execution of non-constant functions and data modification. In general C++ 23. This was the issue in C++ 20 controversial. C++23 solved it to the extent that const elements can now allow modification. So, if you have a sequence that

contains constants in it, a range of constant elements, you can change them because you're plus plus. Again, be careful with competitive access. This is also a very common mistake that people make. Remember that they have reference semantics. Use them immediately after creation. Don't store them for a long time because the container may change or disappear and then you have problems. Be careful, it does solve this problem

to some extent, but it does not guarantee it 100%. And an important thing, when you copy a value to a view, the view itself is copied slightly. This is by recommendation. They are designed to be easily copied, but because of caches you may have behavior problems, have different values, especially if the functions are not unique, have a side effect that is undefined, as we said, and have

different changes in speed. Some caches are copied and operations are then fast, others are not and you may get unevenly fast operations in the same code. The last of the new things. The third idea is a span or area. A span is a very simple idea, which is a view over continuous memory. Things like arrays, character strings, well known from old programming. Aa String Viw is actually,

even though it's called VIW, it's actually span. And this is a guy who is so much older. It's been known since the good old days of aa SPS 17, I think. And he is the only guy, by the way, who lives in standard STD space, not in subspace, because for historical reasons he came from there. A span is just a pair of raw pointers plus a size,

an extremely simple thing, with the size being of two types. It can be static, that is, unchangeable, and this is actually a non-type template argument of our type. Similar to STDR, as well as dynamic, which is stored inside the span itself, in the area itself. which allows you to have a type that can be changed, or used in conjunction with another type of the same, that is,

another area of ​​the same data type with a different size, similar to classic Importantly, they allow you to add constancy to the type during creation. For example, if you have an array of numbers, you can create a range or range array that has constant semantics. It should be constant types. Here they are respected. And how can you create areas? Basically, there are several ways. You may have

fixed-size ones. Here I have some that are compile time, but through just a pointer. Then you specify the size as a template argument. You may have a container that has data and size. Here's the vector example. Oh, here's this example with this array and the vector. Here it is. Here you can take only some part of the elements of your vector. That's right. And importantly, the size

must be known at compile time and the other option is to create a variable size array, where you have a pointer, a data size, and again you can use dat size, but here the size is not required at compile time and in fact you see that you don't have a second template argument. And what actually makes the type different. It is important that here you can assign

both types of areas, in fact, you can assign them on top of each other, and for those with a fixed size you must , of course, have fully matching types. That is, the dimensions should be the same here. Those that are variable in size also carry over, so you can change one span to be linked to other data. And when it comes to appropriation, it's important. They

do not have oni semantics, that is, they cannot possess the domain on which they are defined. They are always just a pointer and a size. When are they used? Very important. They all have two template arguments so that you can pass them to a function that accepts them as a template type. That is, those with variable size also have a template non-type parameter for the size, which,

however, has a special value that says it is unbounded. That is, the size is not set, and you drag it as additional data. Those that are of compile-time size, of constant size, actually only store a pointer. The data itself is extremely tiny. Just a pointer with a bunch of behavior on it. And you can assign them while respecting the size, respectively on two two areas with an

unfixed size. I can always assign them if the types underneath are the same or compatible. Seeds provide multiple methods. By the way, these are the only parts of rangees that allow directly as a to return a piece of itself with a start and end using a spav subspan, sorry, which receive an offset and length. potentially all of these functions like first or last elements have a variant

that takes the count as an argument and creates aa variable-sized area and one that has it as a template argument that creates a fixed-sized area. Below you also have four versions. For brevity, I haven't written them down. Importantly, they are not containers, but they are not pointers. What does this mean? And they don't own the data, but unlike pointers they have a lot of compile-time checks because

of pointers, I mean, because of the concepts, and they know their size. On the other hand, they are designed primarily for speed, not security, like most things in C++. Accordingly, you can copy them easily, you have no checks, access to them is uncontrolled. In most libraries you have a check in theb mode that checks some things, but you don't have runtime checks. It is entirely up to

you to ensure security. You have const, but be careful because again you have semantics that are like pointers. You have a pointer to a constant and a constant pointer. Constant span and span to constants. The behavior is different. They are very similar to the subrange types we mentioned, but they have a few important differences. First, they are lighter. They are lighter because they only contain a pointer.

Subrange defines a very complex type with lots of auxiliary data, iterators, types, and so on. Here we are talking about a simple structure with just one pointer. Second, they do not require an iterator to create, unlike ranges. They can be sold simply by some pointer or container that has simple data and size methods. The good sides of this thing we looked at. A few summaries of all

this. It is much more convenient to use sequences compared to our well-known code, where we have to struggle with iterators. We don't need to keep track of their types, we don't need to compare them. We don't think about prefix or postfix increments and so on. They are more secure because they put in a lot of checks at compile time thanks to the concepts in C++ 20. They

are faster. They are designed for speed, so the same code written with iterating with iterators, iterating through the elements sequentially and applying iterators, the same thing written with views, using, for example, aa sequences of actions pipelines, is much more efficient, because much of the action happens at compile time. Minus the compilation is much slower. So, a few examples that I looked at . Compiling extremely simple things

takes seconds. Ah, I've seen situations for larger projects that use similar things. Compilation takes tens of minutes and consumes gigabytes of Modern, you can see such things everywhere in all languages ​​right now . List competitions, and stream processing, right, all sorts of things. In Python they are extremely modern. CPR+ is not far behind, they have all possible new language capabilities. They are developing very rapidly these days.

They were finally introduced in C++20. By the way, they have existed since the days of Bust. About 20 years ago, C++ finally adopted the functionality from Boost. They appeared in the standard. They use the new things from C++20 very effectively. But they are still a bit ill-conceived. In C++20 they did some things. 23 parts of them were changed. There are still issues to clear up. There is

quite a bit of controversy, especially about const behavior, the constancy of types in the standards community. So they continue to discuss. I don't know what will come out. Ah, these are all wonderful countries. A few simple examples. We have a few minutes. A simple example of finding the first n prime numbers. something that we could actually always do, but here are a few lines written and here

we also find their sum with a very simple application. And the important thing is that here we actually generate exactly as many numbers as we need using generators. It's much easier to record than any cycles. Here we have some function, let's face it, which I haven't given for simplicity, but it checks if a number is prime. And as you can see, you can use functions that have

their own context and actually do things like if you don't have, for example, accumulate, you can do forage with a similar context, it's not a problem. This is a C++20 variant. C++ 23 can now be more elegant. Another example that is also popular is the idea of sequences and views on them, assembling more complex chains of actions that find more complex queries on data. Ah, here I

want to find all the composers, that is, the first three composers, their names from some list. Oh, here I have a filter of those who interest me. I take the first three and apply a transformation to get only the names. I don't care about the other part of the data. An extremely popular concept in many languages. Now also in C++ thanks to the predefined PPE operators. Just

as a note, this thing can be written as aa instead of pipes you can use assembly with more complex direct object construction. The syntax is no different, but it is more complex. This results in an embedding of objects, which makes for a lot of proactive code. And something that displays on the screen all the words that are repeated in some text and their numbers. I won't go

into details, it's a little longer here, but it actually uses the ability to split some text into spaces and then remove the things that aren't letters, because for example you might have commas and things like that. I'm interested in words. A few useful sources. CPP Reference by tradition. This is a very nice source from one of the authors of the very part of the standard, his GitHub.

And of course all the photos of Hoes are taken from the boil to Stocks so that we have something to see. Thank you. [music] Thank you for this lecture. Ah, we're heading to Q&A. I see that there are no questions asked yet on Vivox. If you want, take out your phones, scan the code, ask and vote on the questions you have. I'm taking this moment to ask

you about AI, since we've already seen a few videos. Tell me what you think about it and how will it affect us? Well AI is something that is happening. As the last video we saw made clear. Many people didn't believe that this was really something that would work for us, and it is already a fact. It's in front of us, it's around us. I'm also one of

those people who was more skeptical at first, but I'm already changing my mind. It is actually very important that we learn to use it as a useful tool and not let ourselves fall behind , trying to cut tomatoes by hand, and when we have knives, it is very important to be careful not to cut ourselves with them. It is extremely important for young people, who are still

learning, not to be overconfident and to actually learn to work with what is offered to them as a sample generated code and to be able to evaluate it, check it, change it when necessary, which in my opinion will be the future opinion. We won't need people who write code, people who do code reviews, people who review and fix existing code. This is something that is a future

skill that needs to be developed very strongly. That is, how do people adapt to this change if you've been writing code for a few years now? Well, they need to learn to read other people's code. This is something that is a very, very specific skill, it's not that common. They need to learn a lot more to read the code and be a lot more skeptical. Like, I

don't know, people who don't have a very good internet culture, stumble upon some information on Google and say: "This is what it says on the internet, so it must be true." In exactly the same way, code that comes from artificial intelligence can have serious problems and actually be wrong. This critical way of thinking, approaching someone else's code with care, depth, and understanding is something that needs to

be built as of now. Okay, thank you. Ah, I see there are , I see there are already a few questions. You can reply to WhatsApp and read them. so. Is there any protection against DGL pointers during sleep? But generally speaking, no. It's our job to use them, as well as for the length, when we say we have a span, here's a pointer with 50 elements. Whether

or not there are 50 elements is entirely our And so. Do the views guarantee lazy evolution in all cases? So if it doesn't support an iterator, you won't be able to do it. It has a concept that expects the iterator underneath to support by dirк, that is, the container underneath to support by directional iterator as a type. Accordingly, you will not get a compile error, no copy

will be made. The idea of ​​the views is not to make a copy of the data, but to create maximum reference semantics. Except in the case where you do, which moves the data but does not make copies, that is, it does not add additional memory. Is it worth refactoring old code with the new concepts? I think they need to clean up a bit. And besides, their processing

could have unpleasant side effects. And besides, there's another point that I didn't mention, but C++ 20, that is, your code runs on the old compiler and you have no other reason to migrate to 20, may not have a good effect because you have Breaking changes, things that weren't a problem before are now problems. Absolutely the same for 23. Things that work in 20 may no longer work

or have different behavior in 23, and you have to be very careful when porting to new versions of the compiler over existing code, especially if it's in production , a larger code base, right? I think defbg had a lecture on this topic a while back which was also very helpful. Ah, what should be in STD, but is missing in the latest versions of the language. One of

the things that we expect soon and should have been there a long time ago is a library for working with networks, as well as things that are already on the way, the possibility of vector instructions will soon come out, that is, instruction-level parallelism, but parallelism with a vector instructions. What problems might we have if we try to write parallel versions of view algorithms? The problems are that

views themselves are classes that have an internal representation that is not defined by the standard. And in fact, with parallel access, with parallel iteration, or with parallel reading, it is possible to have a data trace on the internal representation of the corresponding objects. If you don't use standard ranges, write your own types that follow the same concepts, which is not such a trivial task, by the way

. Oh, in a sense, you need to change a lot of trades, a lot of typical features. This can lead to a lot of coding, but in reality you can then write your own save sequences and views. And does compilation time increase when using views on non-static data? Absolutely yes. In a sense, views themselves are a very complex template concept that is hierarchical. Now, if you just

take one view, which is for example r view viw, to take a typing or kow, which gives you two quoerators, the time is short. If you do some kind of special pipeline, this thing takes a lot of time, because in fact, with each use of the pipe operator, you accumulate more and more types. They are placed on top of each other as a decorator pattern, if you

will, or as mats. Each of these can add internal intermediate transformations. The constructors themselves have implicit conversions to improve their types, and you can accumulate six, seven, or even 10 classes because of one or two pipes. And this whole thing is a compile-time process. From the bottom, during execution, the delay is minimal because all of this is stretched to constant expressions and in fact the example I

had with filter and transform is reduced to literally one expression that checks conditions and subtracts the result from it. Literally seven or eight instructions. and like processor instructions, but actually the compilation time, getting to that point takes a hell of a long time and checking all the concepts, they are also time-consuming and complicated. So no matter what you do, compilation time increases. I know those were the

questions.