Transcript for:
Highlights of New Features in C++23

I think it's great. I think it's definitely a good idea if you're looking to get involved in the C++ industry to come, talk to the people, to meet everybody. You never know what can happen. All right. Good afternoon, everyone, and welcome to my session, What's New in C++23? So who am I? I'm a software architect, a project manager for Nikon Metrology based in Belgium and I wrote a couple of books. The latest one is professional C++ fifth edition covering all what is new in C++20. I'm also the founder of the Belgian C++ users group and I try to organize three to four events per year and the next one is actually on 31st of October and there are more than 100. people already registered for it. So let's have a look at the agenda for today. It's quite a lot. So I'll be giving an overview of all the new language and standard library features coming with C++23. Obviously, I will not be able to go into details on all of them, but that's not the goal of this session. The goal of this session is to give you a high-level overview. At least you will get a couple of keywords out of it, and if something interests you, you can always find more information online. So if you look at C++20, that was really a major, a big new standard. It included the big four features, modules, ranges, concepts, and coroutines. C++23 is smaller in scope, but as you can see on this slide, there are still a lot of interesting new features coming. Also, as you can see, I have a lot of material to go to. So please keep your questions for the end of the session. There are slide numbers in the lower right corner of the slide. So you can always refer to a specific slide at the end of the session. All right, so let's get started with the core language features. First one that I want to mention is explicit object parameters. So what is this? So everybody knows if you write a member function of a class, You have this pointer which is implicit in every member function. You don't need to declare it, it's always there. So here I have a simple class cell with one member function set value, and inside, if I would like to, I could write this arrow m value, for example. Using this is straightforward, I can create a cell and then I can call set value on it. So how would you write this with an explicit object parameter? Well, the only thing you have to do is the first parameter to your member function becomes something like this, this cell ref self. Then inside the body of your member function, you cannot use the dispointer anymore. There is no dispointer anymore. You have to use the reference that you have specified as the first parameter, in this case, self. So you write self.mvalue. Using this is still the same. So just like before, In the lower left corner, you can just construct the cell as before and you call this set value the same way as you call it before. So of course you will never use this feature like this. This is just to introduce you the feature. So what can you do then with this feature? First thing is that you can write your refqualified member functions differently. So in the past, if you want to write a refqualified member function, you have to specify the ref qualification at the end of your signature. So here for example the member function f can only be called on l values, the member function g can only be called on const l values, and h can only be called on r values. Now in this simple contrived example it's clear that I have these ref qualifications qualifiers. Of course in real life you will have some parameters, you have longer function names, so during a code review for example it might be Easy to miss these ref qualifications that are at the end of your signature. So you can use explicit object parameters to write this differently. And this is the syntax how you would do that. So in this case, f can only be called on l values, g on const l values, and h on r values. And if you see this in a code review, you can't miss it, right? Because, yeah. It's much more obvious. Another use is if you have overloaded member functions, like in this case, I have a person class with one field, and I have get name member function that is overloaded. I provide three overloads, one for non-const L values, one for const L values, and one for R values. So there is a lot of code duplication here. This one is a very simple example, but imagine that your overloads are much more complicated. So there is a lot of And co-duplication. So, of course, one way to get rid of it is to delegate to a helper method. But that's cumbersome because you still need to write the three overloads and implement them, delegate them to a helper method. With explicit object parameters, you can avoid this. And you can just, instead of having three overloads, you can have only one. And in this context, this feature is called deducing this. So... Instead of my three get name overloads, I write one member function template. The return value becomes outer ref ref. And like before, the first parameter to my function is this self ref ref self. And then in the body of this member function, you don't use this, as there is no this pointer. You use this explicit object parameter. What else can you do with it? You can use this feature to write recursive lambda expressions. Normally, if you have a lambda expression and you use this inside the lambda expression, you will access the object that contains your lambda expression. You are not accessing the lambda function object that generated. So you cannot have a recursive lambda expression. With explicit object parameters, you can. So here I have a Fibonacci lambda expression. And I'm using an explicit object parameter, this auto self. And once I have this inside the body of my lambda expression, I can call self. So I can call the lambda expression can recursively call itself. Next one, if const eval. What is the syntax? It's straightforward. You just write if const eval a else b. As you can see, there is no condition. It's just called ifconst eval. The braces are mandatory with a normal if statement. The braces for if there is a single statement, you don't need them. In this case, they are mandatory. And what's the effect of this? The effect is that if this statement is executed at compile time or in a constant evaluation, then A is executed, otherwise B. So why do we have this? Because we already have a function in the standard library called isconstant evaluated. So what is wrong if you write if isConstant evaluated A, otherwise B? Not much. It's actually equivalent, almost equivalent. ifConstEval is part of the core language. You don't need to include any other files or any modules. Within an ifConstEval block, you can call other ConstEval functions, which is not possible if you use the isConstant evaluated. And maybe more importantly is that if const evil cannot be used wrong, while if isConstant evaluated can. Because I've seen that some people think that isConstant evaluated need to be written like at the bottom line, so if constexpr isConstant evaluated. But obviously if constexpr will execute at compile time and at compile time isConstant evaluated would always be true, so that last statement is the same as just writing a, b will never be executed. So here is an example. I have a const eval function f, also called an immediate function. I have a const expr function g, and in this const expr function, I can check if I'm being called at compile time or not. So one thing, const eval functions, they are guaranteed to be executed at compile time, while const expr functions can be evaluated at compile time or at runtime. So if you want to... execute a const eval function from inside a const exp function, you need to make sure that you are in a constant evaluation context, and you can do that with if const eval. So here in the if const eval, I'm calling my f immediate function. In the else statement, for example, in the else block, I would not be allowed to call f. And I have a third function, h, which is also an immediate function. Yeah, and this one obviously can always unconditionally call. other immediate functions. This is also a nice one, multidimensional subscript operator. In the past, if you had multidimensional data, you had two options. You can either use the function called syntax to access an element of your data or you can use multi-level indexing. Now in C++23, you can have proper multidimensional subscript operators. Implementing them is straightforward. You just implement your operator square bracket as you're used to. Accept instead of accepting one index you accept as many indices as you need and in this case I accept three to access a three-dimensional data structure. Attributes on lambda expressions Before C++23 it was already possible to add attributes for your lambda expression and you specify them behind the Parentheses and the meaning of this is that the attribute has an effect or on the function object that is generated from your lambda expression. So here I'm saying the lambda expression or the generated function object is deprecated. Now at C++23 you can put your attributes on two places. So the first place is the same, you can put them behind the parentheses and then it's the same as before. So it's an attribute specified on the function object generated from the lambda expression. But now you can also put them before the parentheses. And then this attribute is an attribute on the function call operator of the generated function. And like in this example, you can't combine them. So here I'm saying that this lambda expression is deprecated, but if you still call it, you're not allowed to discard the value that is returned by it. So these integer literal surfaces have been there for a long time. So we have U, L, UL, and so on. New in C++23 are the following two. You have UZ and Z. And UZ allows you to make a size D integer literal. And Z creates a signed integer type that corresponds to a size D. Let's look at an example. I have here a vector of data. And in the loop, I'm iterating over all my elements. What I also want to do is I'm caching the size of the data in the count variable. So I say count equals data dot size. And I'm using auto for my variable i, because I want this to work on both 32-bit and 64-bit platforms. But as written here, this will not compile, because i will be deduced as an integer, and count size t returns me a size, sorry. The data.size returns me a size t, so those are two different types, so this will not compile. So in C++23 you can instead write, initialize your i with 0, u, z, to force the i to be a size t, and this will compile fine. So we now have something called dkcopy directly in the language. Obviously we're always able to make a copy of an object in C. C++, sorry. So here I have an object C and I'm making a copy of X. But as written here, C is an L value. It has a name, so it's an L value. With this syntax, auto X, you can either use parentheses or curly braces. You're also making a copy of X, but it is a PR value. So let's have a look at an example. I have a function here, process, which accepts an int R value. and then just prints it out. It's a contrived slide-based example, obviously. And then I have a function processAll that accepts a vector of data, and it loops over all the elements in my data, and it makes a copy of each element as a PR value and passes that to process. So if I would remove this auto parenthesis and just pass i, it won't work, because process accepts, requires an R value. In C++23 we even have a couple of new preprocessor markers. So we already had these ones ifdef and ifendef. 23 adds the following lifdef and lifendef, so they are basically short-hands for the things that you see on the right. And one more, we have warning. So now you can force a compiler to emit a warning. You could already emit an error, but now you can just say you can emit a warning as well during your compilation. Another interesting one is that it's now possible to mark a location in your source code as unreachable. And for this, you use std unreachable, defined in utility, when you actually do... Evaluate this at runtime. You are explicitly invoking undefined behavior. So yeah, so what is this used for? Let's have a look at an example. I have a function here, do something, accepting an integer, and I know that the integer can only be 0, 1, 2, or 3. And then in the body of this function I have a switch statement, and as you can see I'm properly handling all the cases 0, 1, 2, or 3. But the compiler doesn't know that. The only thing the compiler sees is that I have an integer. So if this is compiled to assembly code, the compiler will insert some checks to make sure that the value that I'm passing in is 0, 1, 2, or 3 before it executes the jump to the right case. So there is a slight performance penalty. If I add a default case here and I say startUnreachable, then the compiler knows that the default case can never be reached and it can just omit the check for the jump statement and it can straight jump to the right case of 0, 1, 2 or 3. Assumptions have been supported by all major compilers for a while. MSVC and ICC you can use __assume. Clang you can use __builtinassume. In GCC you can emulated with this expression. What can you do with this? For example, here I have a function divide by 32 and I added an assumption here that x must be positive. And by doing this the compiler can better optimize my code. But as you can see there is no standard way of doing it. Now in C++23 there is and this is the syntax. So you have the square brackets just like attributes. Assume and then your expression and here on the lower right is the same example as at the top And so with this the compiler can better optimize your code. Yeah, so why performance? This is also an interesting one So this is regarding Unicode characters. So here I'm defining a character literal A And I wanted to represent this Latin capital letter A with a macro, which is this thing right here. But obviously, when you write something like this in code, you will automatically add a comment to explain what this 0100 letter is. Here's another one. So I have a Latin capital letter A with macro, and I'm combining it with an accent. So if I write something like this line. initializing B in code, I will always add a comment explaining what I'm writing there. So now in C++23, you don't have to do that anymore. You can directly put the officially Unicode assigned names inside your string literals or character literals like this. So no need anymore to add redundant comments. This is an interesting one. So I have a piece of code here and I have a comment and on that comment line I have a backslash line continuation character followed by an empty space. You don't see the empty space but there is an empty space. So it was undefined by the standard what this code should produce and in fact GCC and Clang would write 1 while MSVC would write 43. That is before C++ 23. Now it is actually required by the standard that all the white space behind a line continuation character is stripped first, and then the output of this code must be one with the latest compilers. So that's it for the core language features. Let's have a look at the standard library ones. First one are a bunch of improvements to string formatting. So C++20 brought us std format, which makes it much easier to format strings. And here's an example. So I have a string, cppcon. The old way of writing this to, well, the old way, the C++20 way of writing this to the standard console is std sayout, std format. And then you can have your string with replacement fields. So these curly braces, those are replacement fields. replaced by the arguments that you pass as second and further arguments. Now in C++23, you don't need std cout anymore, you don't need std format. You can simply write std print, as you can do, I think, in the majority of all the other languages. We can go one step further, and you can write println, and you don't need to specify the slash n at the end anymore. So it's much easier to... Right, it's easier to read, especially for new people starting with C++. But not only that, it's actually much faster than stream IOs, and it's actually an order of magnitude faster than stream IO. It's even a little bit faster than the old C-style printf. What else? So C++23 also allows us to easily format ranges. So I have a vector of pairs of integers here, and I can just pass this to print or println or format. And the output will be as you can see in the comments. So I don't need any looping anymore. It's all handled for you. Same with set. I have a set of pairs of integers here. I can also just print it to standard output. You see the output is slightly different. I have curly braces instead of square brackets. Map also works. Again, The output is a little bit different, so I have the key, colon, and the value, and they are surrounded by curly braces. Between the curly braces representing the replacement field, you can specify format specifiers. They are pretty powerful. So here I have a vector of strings. If I just send it to print without any format specifier, this is the result. So I have square brackets. Every string is surrounded by double quotes and escape characters are output as escape characters. They are not interpreted. If I pass colon colon as a format specifier, then you see the quotes are gone and the slash T is actually replaced by a real tab character. Here I have a multidimensional data. So I have a vector of vector of ints. And if I just pass it to println, I have. the output of the first line. If I pass a colon n as the format specifier, then you see that the surrounding square brackets are gone. I can also remove the square brackets of the second dimension and the last one shows you that you can remove all the square brackets and you can even specify a format for the individual elements. So here I'm saying all the elements must be formatted in a field that is four characters wide and you need to add stars for padding the fields. So I mentioned before C++20 introduced modules. C++23 goes a little, goes a major step further and it introduces two named modules. It introduces std, so you can simply write import std. And then you will get access to everything from the standard library, and everything will be in the std namespace. So everything from C++ will be in the std namespace, but also everything from C will be available, but only in the std namespace. There is a second named module called std-compat, which imports the same thing on std, but it also makes everything from C available in the global namespace. So I discourage using this one. it's better to use the import std. So this combined with the print and the println that I explained before, I think this one is now the new modern way of writing hello world in C++. You simply do import std and you use println to print your string. So with C++23 you can change, we changed half of the lines of our hello world program but i think it's much more clear what is happening C++23 introduces a couple of new containers, flatmap, flatmultimap, also flatset and flatmultiset. What are they? They are all adapters on top of other basic sequence containers. So flatmap is very similar to stdmap. It provides you an associative interface, so you have keys and values. Everything is sorted on the key, and you have very fast retrieval of a value based on a given key. And of course, you can change the underlying sequence container. The thing is that FlatMap and FlatMultimap, they use two underlying sequence containers. So one sequence container to store your keys and another one to store your values. And both of them, you can choose whether you want them to be a vector or a deck. FlatSet is very similar. And you can compare it to... the std set. Their use is straightforward. If you know how to use the normal map, you can use a flat map. So here I'm just creating a map with integers as key and strings as values, and I can use it as the normal std map. And this is the way how you can specify a different container for the keys and a different container for the values. C++20 gave us std span, and C++23 expands on this and provides or gives us md span, which is a multidimensional span. The thing with md span is it supports different layout policies. And what is a layout policy? A layout policy is a function that will convert a multidimensional index into a one-dimensional offset. And you also have a sub-md span if you want to. have a subview. So let's have a look at an example. So here I have some data, it's going from somewhere, that's not important. But let's assume that that buffer represents actually a two by two array of integers. So then I can create an MD span, and I pass my buffer as the first parameter. And then I specify two indices or two sizes for the two dimensions. So in both dimensions, my data has two elements in this example. And then once I have this MD-SPAN, you can iterate over it. You can ask the size or the extent of each dimension. And then this way you can get to all the elements. And you can also see here that it supports the C++23 multidimensional subscript operator. So generator is a standard coroutine. Writing your own coroutines is difficult. C++20 didn't provide any higher level coroutines, so now in C++23 we do have one, which is generator. So here I have a function getSequenceGenerator which accepts two parameters and in the body I'm just looping from start value to a number of values and on each iteration I'm returning one value to the color of this generator. In main I can create my generator and then iterate over the values of the generator. What this is doing is on each iteration in main, my coroutine will resume, it will do the next iteration, and it will return or co-yield the next value of the sequence to main, which then processes it. In this case, it's just printing out the value, and then main will iterate again and resume my coroutine again. This is also interesting. So basic string and string view, they both have a new member function called contains. So now you can, if you have a string, you can easily check if it contains a character or another string. So here I'm checking if my haystack contains the word world, which is true in this case. Here I'm checking if it contains the character exclamation point. You can also check if it contains, if you have a string view, you can also check whether that is contained in your string. So there is no more need to use find and then compare the result of your find function with the end iterator. This in C++20, you could write something like this. Compiler will compile it, but it's undefined behavior at runtime. Now in C++23, this will not compile anymore. It will result in a compiler error. Resize and override, this is more for if performance is important. And I think it's easiest if I, well, on the next slide, you will see an example. So what does this do? So basically, it's going to resize a string with a new size, count. And it will use the operation, the op that I pass here, to fill in the data of my string. So what is the effect? If count is less than... Or is the new size smaller than the current size of my string? Obviously, I'm just going to delete some characters. If the new size is bigger, then I'm going to make my string bigger with default initialized characters. And then I'm calling my operation. And the operation that you pass to resize and override accepts two parameters. The first one is a pointer to the data, to the string. And the second one is the size that... we want the string to be. But this operation then fills in the data and if it decides that the string will be a little bit smaller than what is requested, it can return the new size. And the r that you see there is the size of the final string. And so then the last step that we have to do is if r is less than count, we need to trim the excess characters. So this all sounds a bit abstract so... Let's have a look at an example. I have a function here, generatePattern, and it will return a string which contains the given pattern repeated count times. So how to do this? It's straightforward. We just create a result string that at the end we will return. We reserve enough memory for performance, and then we loop count times, and on each iteration we just append another pattern. So what's wrong with this? There are a couple of performance issues here. So on every append, we're writing a null character. And on every iteration in this loop, we're updating the size of my string. And every time, we're checking if we do need to resize the string. Even though I reserved enough space, we're still checking if we do need to resize it. So in certain cases, this is not optimal. And for that, we can reuse resize and override. It's a bit harder to use, but... And so here, the operation that I talked about is given as a lambda expression. The first parameter to this lambda expression is the buffer, pointer to the buffer. And the second is the size of my string. And then inside this lambda expression, you can do whatever you want. You fill in your data in your buffer. And the thing is, there is no bookkeeping. So there are no redundant nulls being added here. There are... The size of the string is not constantly updated. We're not constantly checking if the string needs to resize. Though I must say I would only use this if performance is really critical because I find this much harder to read than it was on the previous slide. So first write the previous slide, profile it. If it's really necessary, you can use this to optimize it. So when you're using std optional in a chain of operations, before C++23 you always have to check if intermediate optionals were empty or not, because before you call the next operation on your chain, you should make sure you actually have a value in your optional. With monadic operations introduced in C++23, those checks are happening automatically for you. You don't need to explicitly check for an empty optional. And there are three monadic operations. The first one is transform, and transform returns another optional that contains the result of invoking a function f that I pass to transform. If my optional has a value, if it doesn't have a value, transform will automatically return an empty optional. The other one is end-then, which returns the result of invoking f if we have a value, otherwise it returns and emptyOptional. And then finally we have orElse, which returns the value, the optional, if it has a value, otherwise the result of invoking f. But this sounds a bit abstract again, so let's have a look at an example. I have a function parse here that accepts a string as parameter, and it just tries to parse the string as an integer. If it fails to parse a string as an integer, it returns an emptyOptional. Now in my main function here, I have a while through loop and I'm just asking the user to enter some data. And for each time that the user enters some data, I'm trying to parse this as an integer. So I'm calling my parse function, which returns an optional. Now when the parse is successful, what I want to do is I want to multiply the integer that the user entered by two. So I just write and then. So if my optional is not empty, it will execute this lambda expression and it will multiply my value by two. And after that, I want to convert my... calculated double value or my double integer back to a string. So I use transform to transform my integer value into a std string and if the user did enter something that I could not parse as an integer then you use r else and in this case or else just returns me an optional containing the string no integer. And then the last line just prints me the contents of my result and that is that is an optional so you see here um there is nowhere that i'm checking for an empty optional i'm just chaining all the operations and everything is happening behind you for you so so here is an example so if i write 12 on the console this program will write 24 if i write something like ee it will write no integer Stacktrace is new, so we can now finally work with stacktraces in our C++ programs. To get the current stacktrace it's easy, you just call std stacktrace current and then you can just throw it into println and it will print you a nice stacktrace with all the frames and all the source files and the lines that are in your stacktrace. What can you do with it? One nice use case is if you can write your own exception that automatically embeds a stack trace. So here I have my exception. I have two data fields, a message and a stack trace. And in my constructor, I accept the message and I accept the stack trace. But the trick is that you specify a default value, which is the stack trace current. And then the constructor just moves the message and the stack trace into my. into my data members. Now we have a couple of getters. By doing this, whenever you throw a myException, like here in bar, when you catch it, in main I'm catching this, you can ask for the exact stack trace where myException was thrown. Of course, you can do more than just passing the stack trace to print or println. You can iterate yourself over all the frames. So here, again, getting the current stack. trace and I'm printing the, I'm iterating over all the frames and printing their description. Ranges was another big thing in C++20 and in C++23 we have quite a few new additions, both to the ranges and to views. So let's start with ranges. So now you can check if one range starts or ends with another range. So here I have a vector v1 and v2. And I can ask if v1 starts with v2, which is true in this case, or if v1 ends with v2, which is false in this case. You can shift elements in the range now, left or right. I have a vector of strings here, a, b, c, d, e. I can shift everything by two elements, and then the result is c, d, e, and two empty strings. I can shift them right, and then I have the empty string, c, d, e, and the empty string. Ranges2 allows you to convert the range into a container. This was not so trivial in C++20, so luckily now we have this ranges2 in C++23. So what am I doing here? I'm using IOTA to create a range of four integers, one, two, three, four. I'm transforming them. I'm doubling them, so I get two, four, six, eight. And then the second or the third line here converts my range into a vector, and then I get a vector of integers. I can print them out and you can see that the result is 2, 4, 6, 8. But you can do more than just converting using it this way, because every container is also a range, so you can convert one container to another easily. So here I have a vector of integers and I'm using ranges2 to convert my vector of integers into a set of integers. And you can do that as you can see with a single statement. Another example, you can use the pipe operator of ranges. So here I also have my vector of integers. And in this case, I'm piping it to range 2 for a set of doubles. So with this one statement, I'm converting my vector of integers into a set of doubles. This is a third way. You can also use range constructors that have been added to all containers. And here you use the from range tag as the first parameter to your constructor. So I'm calling a constructor of set doubles and I'm passing it a vector of integers. One more. I have a string here. I'm splitting the string on spaces. And then I'm going to convert each of the words that we found into a string. And then finally I convert this to a vector. So the result of words is a vector of strings containing all the individual words. And if I pass this to print, you see that you have all the individual words in the vector. There are a few new find functions, so you can do find last, find last if, find last if not. They return you a subrange, starting at the element that was found all the way to the end of the range, or last, last, if whatever you were searching for cannot be found. You can also do contains, just like with a string and a string view. On a range, you can now also call contains, which returns true if a range contains a given element, or you can use contains subrange if you want to see if a range contains a certain other range. somewhere in as a sub-range. For example, I have two vectors here, and the first println statement is checking if v1 contains the element 22, which is true in this case, and the second is checking if v1 contains the v2 range somewhere in its range, and in this case that's also true. Folding algorithms, so I'm gonna... Read them all, but in C++23 we have all these fold algorithms. So let's look at an example how they can be used. So you have a vector of integers 11, 22, 33, 44, and I'm folding them left, starting with the value 0 and using the operator plus. What is this doing? It will start from the left, so it will take 11, and it will add 22 to it, plus 33, plus 44. So the result is 100%. The views library, there are lots of new functionality in the views library. The first two are zip and zip transform. I'm not going to read the definition. It's easier to have a look at an example. I have three vectors here. V1 is a vector of integer. V2, vector of characters. And V3, another vector of integers. So if I call zip with V1 and V2, then... The resulting view will contain two tuples. It will take the first element of v1 and the first element of v2, and that will be the first tuple in my view. And then it will take the second element to mb, and that will be the second tuple in my resulting view. With transform, I can pass an extra operator that is applied to corresponding elements. So here I'm doing zip transform on v1 v3 which multiplies as the operator. So it takes the first element of v1 and multiplies it with the first element of v3 resulting in 3 and then it takes the second element and the result of this is 8. Adjacent adjacent transform. Let's look again at an example. I have a vector of integers here. If I call adjacent with template argument 2, it will make a view of tuples, in this case pairs, and you can see it takes the first element of my vector and the second element and does the first tuple, and then it slides along. So the second is 2, 3, and the last is 3, 4. Transform is again where we specify an extra operation that is performed. on the elements. So here I'm saying multiply the elements so you can see that it takes the first element and second element multiplies them together resulting in two and then it does two times three with six and three times four is twelve. Pairwise and pairwise transform. So often you will use adjacent with template argument 2 or adjacent transform with template argument 2. So there are two aliases defined for it, pairwise and pairwise transform. So let's look at another example. So here I have v2 and I just say pairwise so then I get a, b and b, c. Here I have another vector and I'm saying Pairwise transform with plus operator this time. So it takes the first element and second element, adds them together, results in seven, and then four plus five results in eight. Slide. Slide is similar to adjacent, but adjacent, as you remember, requires the window as a template argument. For slide, it's a runtime parameter. Other than that, it behaves the same. So here I'm doing slide two, and you see it's similar then. compared to the adjacent two. Chunk, so I have a vector of integers again, one, two, three, four, five, and I say chunk by two, and you see that the result of this is that it takes the first two elements of my vector, and that will be the first tuple of my resulting view, and then it slides along, and then the second tuple will be three, four, and the last one will just be five, because I have, in this case, an odd number of elements. chunkby is similar compared to chunk but you have a predicate to decide how to make your chunks. So if I have this vector and I pass less equal as my predicate then it will start creating the first tuple and it will contain 1, 2, 2, 3 and then the next value in my vector is 0 which is less than or equal than my 3. So I'm creating a new tuple. And so your data in your vector is chunked by the predicate that you pass to chunk by. Join with. So if I have a vector of strings in this example, I can say join them with the new line character. And then when I print them, you see that... All the words in my vector have been joined with the new line character in between them. Strides, so if you have some data and you want to skip certain data, you can use strides. So here I'm saying stride two, so it will skip every second element. So the resulting view will be instead of one, two, three, four, five, the resulting view will contain only one, three, Repeat, you can use repeat to either create a bounded view of a certain element. So the first repeats the element two, three times. You can also use it to create an infinite view. So the second or part 12 is an infinite view of the number two repeated an infinite number of times. Cartesian product. So the result of the Cartesian product is, yeah. is a Cartesian product. So here, this example is just calculating the Cartesian product of a vector with itself. The vector contains 0, 1, 2. So the result of this is, as you can see in the comments, 0, 0, 0, 1, 0, 2, and so on. As r values, so here I have a vector of words, vector of strings, and I have another vector of strings, which is for now empty, called moved words. What I want to do is I want to move all the words from the first vector into the second vector. So you can just take words and then pipe it through the view as our value and then it will move every word from words into the moved words vector. Stood expected is a new vocabulary type. So it accepts, it requires two parameters, T and E. And T is a type of the expected value, and E is an error type. And the nice thing about this is that stood expected, an instance of this is never empty. It will always contain either an instance of the expected type, T, or it will contain an error. It's never empty. So let's have a look at an example. Here I have an expected. type and the expected type is an integer and my error type is a string. And if I want to initialize this with value of the expected type, like in this case 21, I can just do it like this. If I want to initialize it with a type of the error or the error type, then you have to use std unexpected. Now what can you do with this? You can call has value on it, which will return true if your expected contains a value. of type T. If that's the case, you can call value on it to get the actual value. If you call value and you don't have an instance of type T in it, you will get an exception, bad expected access. You can also call error if you want to return the error that is inside your expected. Now error, you should only call when there is actually, when there is an error. So std function, I'm sure everybody knows about std function. So here I have a process function accepting a function callback, which doesn't accept any parameters and returns me a string. I can call this as follows. I can call process with a lambda expression, straightforward. But this fails. So if I'm trying to call process with a lambda expression that has a capture variable. be initialized with make unique, then this will not compile. Why? Because to pass this to my process function, std function will try to make a copy of my lambda expression. And since my lambda expression will have a unique pointer inside it, you cannot make a copy. So it will complain that attempting to reference a deleted function. In C++23, there is this move only function. So if I change my parameter type of the process function to std move only function, then both of the examples from the last slide will work. The first one obviously still trivially works, but now the second one as well because of the move only function. Span stream. Span stream allows you to use stream operations on external buffers. So let's look at an example. So here I have some buffer data, and I can use an ISPAN stream to parse what is inside my buffer using stream IO. So I'm just wrapping my data in an ISPAN stream, and then you can see I can use the normal stream extraction operators to extract AMB. The other direction is also possible. I have an OSPAN stream, so then I can just use the stream insertion operations. on an external buffer. Byte swap is defined in bit and it's a standard way to swap a byte. So before this there was no standard way to swap a byte. If I have an unsigned 32-bit integer here, if I print it using println, the output will be one two three four five six seven eight. If I swap the bytes you see that the result now is, yeah, the bytes are swapped. This is now the standard way to swap bytes. ToUnderlying is defined in utility. What it does, it converts an enumerator to the underlying type. So in the past, you could do something like this. You can do a static cast to the underlying type T of E. E is my enumeration type. of a certain value. This is ugly, so let's look at an example. So here I have an enum class color with the underlying type a 32-bit unsigned integer and I have three colors red, green, blue and then I define a color r initialized with the color red. So then when I want the actual 32-bit color of my r color in the past you use this static cast underlying type t. Now you can simply write std 2 underlying r and you will get the 32-bit unsigned integer of the color red out of it. So we already have for a while, I don't remember exactly which standard introduced it, but we already have for a while heterogeneous lookup. What does this mean? So if I have a set of strings, if I have a set of std strings, and I would call find on this set, and I'm passing a C-style string, then in the past, this would create a temporary string. So it will take the C-style string hello, make a temporary std string, pass that to find. So there is some overhead involved here. Thanks to heterogeneous lookup, that's not the case anymore, so there is no temporary string. created anymore. But so this is already available since a couple of standards. What's new in C++23 is that we now also have heterogeneous erasure and extract. So if I would call erase or extract on my set in this example and I would pass a C cell string, then also in these two cases there is no temporary std string created, so it increases performance. That's it for the standard library features. There is actually one feature that has been removed totally, and that's all the support for garbage collection. Probably a lot of people don't know that there was support for garbage collection. This was introduced in C++11. There was this garbage collection API available, provided by the standard, but we're not aware of anyone that is using this API. Nobody knows about it, I think. Does anybody know about it? No. So, it's removed in C++23. So, you can no longer use it. And that's it. So, right on time, I think. So, if there are any questions, we have, like, four minutes for questions. Hi, thanks very much. It was very informative. We covered a lot in such a short time. So my question is around just the repeat and then creating essentially an infinite view and then trying to convert that to a vector using ranges2 or something like that. Is that defined behavior or is it just going to loop forever out of memory? Or do you know what's going to happen? I think that's not going to compile. You will always have to limit it. For example, you generate your infinite... Infinite sequence is the same with IOTA. You can also generate an infinite sequence. And then you can do something like take five or take while or something like this to limit the sequence. And then you can call ranges two. All right. Thank you. All right. No more questions? Then everything was clear? Oh, another question? Okay. Does range two... Cause evaluation of a pipeline. Yes. It converts it to a container, so it needs to execute. Yeah, I understand it converts. But if you just say vec pipeline to something, will that be evaluated at that point? Yeah. Yeah. Because the result of that is really your vector or whatever container, but yeah. Hey, great talk. Thank you. Very informative. I just wanted to mention that back on slide 62, I think there might have been... I think your and-then example, I just have a suggestion. I think it could have just as well been a transform. And you might want to pick something for and-then that... So the slides disappear? 62, I think, with the std optional... Yeah, I'm showing 62, but it's not a parallel. appearing anymore. It was the parse function, right? Parsing the string into an integer. And then never returned the empty optional, so it doesn't make the full use of that. Sorry, it does? There was a try-catch in the parse function? Oh, yes, but in your and then lambda, it just returns two times value, so it never returns the empty optional. True, true. If the parsing succeeds in this example, so if parse returns me a real optional, In this case, it will never become empty because I'm just doubling it and transforming it to a string. But you can imagine that my end then would do something and decide that it will return an empty optional. That's perfectly possible. But then still, I wouldn't need to check for the emptiness to continue my chain of operations. I'm just pointing out your lambda doesn't. So it could, but... Yeah, yes. True, true. I could change it, yeah. Thank you. All right, all right, thank you everyone.