LongCut logo

What Makes Modern C++ Compelling For Programmers? - Gareth Lloyd

By ACCU Conference

Summary

Topics Covered

  • C++ Delivers Modern Ergonomics
  • Chrono Evolves to Full Calendar
  • Operator Overloading Enables DSLs
  • Constexpr Shifts Runtime to Compile
  • Format Strings Checked at Compile

Full Transcript

So I'm G. F L F L F L F L F L F L F L F L F L F Lloyd I actually run these AO York meetups. I'm also a senior software

York meetups. I'm also a senior software developer at a database company called Membr. And this is my little talk called

Membr. And this is my little talk called is that C++?

So at the beginning of the year I wrote this bit of code and all it does is print out all the dates and times for all the Aku York meetups happening this year. And so to answer the questions at

year. And so to answer the questions at the beginning of the slide, is this C++?

The answer is yes. And you know, some of you in this room have never done C++ and you're like, so what? Others of you in the room have seen C++ before. And

you're going, wait, that can't be C++.

That's way too short. Uh what what the hell is that feature? I've never seen that before. Um but this is, you know,

that before. Um but this is, you know, code that I'd expect somebody who's up to date in the C++ language to be able to write. And it's actually quite

to write. And it's actually quite concise, ergonomic in my opinion. So I'm

going to actually use this as an example to talk through some aspects of C++. So

for if you're not familiar with C++, it will actually probably tell you one or two things about it that you didn't know.

So let's start off. First of all, C++ is a strongly typed language. All right, I get that it has a history with C and C has a lot of implicit conversion. It's a

weakly enforced language. But C++ is a strongly typed language and weite kind of avoid all those implicit conversions as much as possible in order to make you know robust libraries that you can use.

So this is true in many programming languages. You do not want the integer

languages. You do not want the integer 2025 to represent years. You actually

want a strong domain type. So this will actually be stood chrono year. Um but

you know that's a really big mouthful thing to construct and type. So actually

there's a really good ergonomic feature within C++ of userdefined literals and you can do it for you know arbitrary types uh but this one is for stood chrony year and it has a suffix y. So

you get this nice ergonomic way of constructing with no real overhead. It's

just um nice syntactic sugar for constructing a stood chrono year. I've

kind of like jumped over a bit of C++ history because it's not like C++ came out you know with every single feature that you wanted straight away perfectly.

So in C++ 11, yes, we did get chrono, but when it came out in C++ 11, what you what you got given were um like clocks and time related things. So you could

ask a clock for a time point and you could actually play around with like hours minutes seconds milliseconds nanconds, etc. And they're all about duration. And you got all the methods

duration. And you got all the methods around these types such that it's stopping you from doing the incorrect thing. So I can take a duration and add

thing. So I can take a duration and add a duration and get back a duration.

perfectly fine. I can have a time point and a duration and get back a time point. Also fine. If I try to take a

point. Also fine. If I try to take a time point and add a time point, that's a compilation failure. So using strong types, and this is not true just true for C++, it's true for many languages,

using strong types to prevent you from uh doing the wrong thing.

And it wasn't until like C++ 14 that we actually started getting the userdefined literal in. So we've got the time

literal in. So we've got the time literal. So now you can actually in a

literal. So now you can actually in a very ergonomic way spell out 18h + 30 min. And that tells you 6:30 p.m.,

min. And that tells you 6:30 p.m.,

right? And then eventually after many years, we get to C++ 20 and we actually get dates. So all the calendar stuff. So

get dates. So all the calendar stuff. So

we can start talking about years, months, and days. And in fact, if you take a look at like line 15, uh, line, no, line 17. uh we can talk about the

first Wednesday of a month which is very different from like on line 25 where we turn it to local days and that actually turns it into for example today

September the 3rd an actual date and we can still like add that onto a duration to get a time and that time isn't based anywhere in the world. So in order to actually say that this meetup exists

somewhere here in York let's tie it to the Europe London time zone and so we actually have zone time. So you've got all these strong types making sure that you can do everything correctly.

I kind of already hinted at this, but I'll explain it in a bit more detail. So

C++ has overloading. What this means is that you can have multiple functions with the exact same name and their different implementations. And the way

different implementations. And the way that you statically differentiate between them is the arguments that you pass in to that function. So if you had a function that took in an integer uh

called I know fu and then you had uh another function which took in a double called fu you could have two separate implementations for that and the overloading would pick the right one statically based on the types that you

passed in.

Operators are nothing special. They're

just another function. Um but they allow you to have like little syntactic changes. So you can actually have

changes. So you can actually have prefix, postfix or infix operators. So

operator slash is one of those infix operators and this allows us to have the ergonomics to be able to compose like dates and uh stuff with a nice concise

format. So we can say 20 25y/ February

format. So we can say 20 25y/ February and that's a very nice way to construct a year month.

The variable that we've assigned this to um we could actually spell it out in a very long form. We could say uh stood chronoy year month. But you know that's noise again. So in order to keep things

noise again. So in order to keep things nice small and concise, we can actually use placeholder type specifiers which also came out in C++ 11.

Um this isn't like some magic variable that can take anything. It is determin deterministically uh done by the compiler that what's on the right hand side of the expression it is a stood

chronoyear month and that variable will only ever be a stood uh chronoyear month.

So I'm going to get uh a bit of a side step away from this code to show you a few more things that have happened over the years in order to make the ergonomics of C++ better.

So pre C++ 11 um the standard template library is brilliant. It's wonderful. We

have generic containers and we have generic algorithms. Um but in order for them to work together there's a concept in C++ of iterators and that is a like an abstract way that they can

communicate to each other to say this is how the container is navigable and how do you read elements out of that container.

Um but PC++ 11 you'd have to spell out that iterator type in its full form. So

you go stood vector int con iterator.

That's a mouthful. So already you can see just by using auto it makes this simpler. We know that CB begin is going

simpler. We know that CB begin is going to give an iterator. We know this is a C++ language and we know about iterators. We know that's going to be an

iterators. We know that's going to be an iterator.

This is also a very common iteration pattern. You're going through a

pattern. You're going through a container and looking at everything in that container. So why not have some

that container. So why not have some syntactic sugar for it because it's so common and that's what we've got. So we

have this rangebased for loop and now we no longer need to actually specify anything about the integers. It's all

hidden away syntactic sugar behind this.

Uh so we still have the container that somehow behind the scenes is getting those iterators. We're going to iterate

those iterators. We're going to iterate through those and dreference it to get our value out of it.

Whilst we're talking about cleaning up code, if you take a look at line five and six, we are constructing a vector and then we're actually going to push back a value onto it. And that seems a

bit, you know, bit too much. Two lines I know is too much, right? Um so what we can do in C++ 11 is actually tell it these are the values that I want you to construct at construct time. Okay so I

can say with an initializer list which all of that initializer list contains is the value 42 at construction time.

Please populate that 42. So it does it all in one go. You'll also notice in the previous slide I had to spell out explicitly that this was a so vector is a generic container. It is a generic

type on there to say that all the elements inside of it are going to be intra. And I had to explicitly say that

intra. And I had to explicitly say that here. On the next slide, I'm using C++

here. On the next slide, I'm using C++ 17's class template argument deduction in order so I don't need to do that. And

there's a bit of machinery going on. So

the initializer list is also templated and it's saying it's initialized a list of integers. And automatically at

of integers. And automatically at construction is tying this together to say, oh, I know what you mean here is that it's going to be a stood vector of integer. Again, making things concise.

integer. Again, making things concise.

For completeness, me being a performance freak in this little demo thing, I I have to say I'm going to use array instead. So, a vector is a dynamically

instead. So, a vector is a dynamically allocated array. It's on the heap. If I

allocated array. It's on the heap. If I

was going to do this in some production code, it'll be a stood array, which will do it here on a stack instead.

Okay, so I've already talked about a bunch of things that you can see in other languages, strong types, a bunch of features that make things a bit more ergonomic that you can actually uh have

smaller code and less boilerplate.

But what is it that draws me to C++? And

it's this idea that I can actually move stuff from runtime that doesn't have to be done at runtime. It's metaroming. I

can do that at compile time instead. So

what this const expo is doing is saying this variable that I'm initializing right now make sure anything on the right hand side of this expression must have been done at compile time. And what

this means is that all the code that comes after this that depends on first month, it can actually inline that value and have better codegen because of it.

Okay, so let's talk about a bit more evolution of C++.

This is what metaromming used to look like. This is known as template

like. This is known as template metaroming. And you you'll notice

metaroming. And you you'll notice compared to many other languages, generic systems. So C++, not only can you pass types in the generic arguments, we can actually pass values. Not many

languages can do that. Um there is restrictions on what values you can pass, but here we're just having an intervent. So at the bottom on line 17

intervent. So at the bottom on line 17 when I'm instantiating fib 10 and then getting out the value from that that goes to the the top template uh right at the top and that recursively

instantiates fib 9 and fib 8 and it keeps on recursing all the way down until we get to the template specializations fib 1 and fib 2 which have hardcoded their value to be one and this all bubbles all back up to say 55

at compile time.

Put your hands up if you think this is code that you want to write on a day-to-day basis. Right, there you go.

day-to-day basis. Right, there you go.

No, no hands were shown.

So this is how we do it today. And so

this is a constexer function. So it

looks like normal C++ apart from all we've done at the beginning. We've put

const expert. And what this basically means is this implementation of fib we can actually run at both runtime and compile time. And to do that we've

compile time. And to do that we've actually said um we've got some restrictions on what we can do inside that function. For example, you can't

that function. For example, you can't allocate at compile time and deallocate at runtime. That makes no sense. So

at runtime. That makes no sense. So

context is protecting you from a whole bunch of undefined behaviors etc. So on line 11 when I have this context per variable it's forced that that fib 10 must have happened at compile time. So

this is the way that we do uh meta programming today, right? So all this has built together to

right? So all this has built together to this big thing here. So what we're going to do is, you know, I say it's big, it's it's printing out a string, right? Uh

but we're actually going to tie uh this whole idea of having some ergonomics, some uh moving stuff from runtime to compile time and try to do some good code. So what's happening here? We've

code. So what's happening here? We've

got print which is also got a format string. So it's not actually a format

string. So it's not actually a format string straight away straight away. We

this is one of the few cases where you actually get a little implicit conversion that is actually cringing literal that gets implicitly converted to a format string but it uses stuff that print is saying in order to make

that work. So it's found in uh that

that work. So it's found in uh that format string that there's two placeholders, right? And both of those

placeholders, right? And both of those placeholders are referring to the zeroth uh argument. So print has play said you

uh argument. So print has play said you know these are the arguments there's only one of them so in the zero place there is something great at this point we do not get a compile time failure there is at least the zero argument for

us to do our formatting with the zeroth argument meetup happens to be type zone time so again the format string is going to go away and say is there a formatter for zone time and yes at compile time it

can find a formatter all good it's then going to say right I'm now going to pass this at compile time percentage b% percentage D, percentage R, percentage Z. Do those formatting options make sense? And it goes yes,

wonderful. So, it's going to make some

wonderful. So, it's going to make some highquality code that can be run at runtime, which will extract only what's needed out of the Meetup variable for because it's a zone time type and then efficiently fill in your print buffer

with whatever is needed to do that at runtime. So, this is a very efficient uh

runtime. So, this is a very efficient uh code gen going on from this.

Okay, so I just talked through that bit of code and hopefully almost every line um should make sense to you now. Um

yeah, sure, many languages have strong types. Many languages have a lot of

types. Many languages have a lot of ergonomic features. Uh fewer languages

ergonomic features. Uh fewer languages have the ability to move stuff from runtime to compile time. And that's one of the major reasons that I have for using C++. Um and even if you aren't

using C++. Um and even if you aren't going to use C++, never stop learning.

This stuff can be just a little thing on the side of your brain going actually that's a different way of approaching this problem.

This is what it actually printed out.

And as you can see, we have September the 3rd, uh, 18:30 British summertime.

That all worked out nicely. And this is my pitch to say, "Please come along to the, uh, meetups in the rest of the year." There's one in October, one in

year." There's one in October, one in November, and one in December.

Uh, I do actually have a couple more uh, little bits of C++ code that I can show if there's no questions. Um, because

these are things that exist in C++ that you might not find in many other languages. So, let's first of all, any

languages. So, let's first of all, any questions?

First of all, did did I understand right that um you you showed format strings are checked at compile time.

>> Yes.

>> If you write that format string wrong, it won't compile because that's really cool. I might become a C+.

cool. I might become a C+.

>> So if I put percentage f in this, I get a compile time failure.

>> I'm over cotlin now.

Second question. Um, do do you think that um operator overloading can go too far? I mean, some of those operator

far? I mean, some of those operator overloads I look at and I think the slash for the year, month, day, and the indexing operator for the first Wednesday of the month, it's not

entirely intuitive because that's not doing what the operator would normally do. It's not doing division or array

do. It's not doing division or array indexing. Do you think that's a valid

indexing. Do you think that's a valid criticism of kind of going so far down this route? But this is true in many

this route? But this is true in many languages where you down in a like a domain specific language like you are reusing symbols and tokens in order to make something ergonomic within that

domain. And I don't think this is a bad

domain. And I don't think this is a bad ergonomic decision for saying, oh, rather than having to say dot um add month, dot add day, like it's an

alternative syntax. And

alternative syntax. And it it may be obvious to you or it may not be obvious to you, but it I think it depends on what the the code domain you're working with. If you're doing

stuff with times and dates all the time, this is going to be like the way you want to code. If you're not, then maybe you want to do the more verbose way because it's not that common in your codebase. Therefore, you want to spell

codebase. Therefore, you want to spell it out in a long form with constructors and with uh you know dot method, etc., etc. >> That's a fine line, isn't it? It's

sometimes you step over. If those were paths, would you complain for example?

>> I mean, I I don't know if I'm necessarily complaining. I'm just

necessarily complaining. I'm just curious where where the line is.

>> Point is, it was you'd recognize it much much more.

>> Yeah. Yeah.

>> Yeah. I mean, have have you have you seen code where you think, "Oh, they've gone too far. That operator doesn't make sense."

sense." >> I mean, the the the first complaint in all of C++ Yeah, the first complaint with uh C++ history is the first big usage of this was um streams. So, right

now we we're now using like print. It

used to be that you would streaming things in or out and you'd have like the bit shift operator, the the chevrons and that was like practically the only usage that it was within the standard library.

It was weird and people didn't like it.

Um, and there's other libraries which I think go too far. So I think there's I think Boost Spirit comes to mind. Yeah,

I'm getting a nod. Um, so yeah, Boost Spirit really goes to town, but it's trying to again it's a DSL. It's trying

to um make a particular domain take less code to type. Um, yeah.

Anybody else want the mic?

uh more an observation I find funny out of all this like really nice and quite complicated syntactic sugar and stuff that's making everything really complicated things in a really difficult

way make it simple is it true that the most recent and futuristic thing that it was on everything you explained was the print statement in 23

>> yeah that's the last thing they made >> everything builds together so in in what I kind of hinted at here or you know where was it yeah Yeah. Uh the fact that

we can pass values in template arguments. Um over time uh you know it

arguments. Um over time uh you know it started off with just integers and uh other things which were very simple. Uh

and now we've got to an idea that anything that's a structural type that you know bitwise that that is fine. Um

oh sorry knocking my mic. Uh and that's what's needed in order to make that work. So that actually translates this

work. So that actually translates this value known at compile time into something that can be templated. Um

because what's actually happening is translating that into some other magic types. Yeah. But yeah, that's what it's

types. Yeah. But yeah, that's what it's doing.

>> Yeah, makes sense.

>> Cool. Right. I'll just quickly show you uh those couple of C++ demos just in case uh they spark your interest. So

this is using a different feature of C++ uh known as um a fold. So I can actually go uh again template thing these are all different numbers. I can go from one

different numbers. I can go from one multiplied by 2 to 3 4 5 6 to I. Uh and

so this is a fold expression happening at compile time. So I can actually do the Fibonacci know the factorial function at compile time in this way instead. Um it's not a nicer syntax but

instead. Um it's not a nicer syntax but it's a way of doing it.

And then other libraries that come quite nice. So

uh there's ranges library where I can actually zip two bits of data together and then I can sort over the zipped thing. And so I can do a projection just

thing. And so I can do a projection just over the first bit of the key and that's going to sort both this collection and this collection. And so by the time I

this collection. And so by the time I look at the uh data later on, it is expected and it becomes sorted. So I end up sorting both of these but by a key.

And I know there's libraries out there that do this, but I think the way that the C++ libraries are being built up uh make this nice going forward.

All right, thank you very much.

Loading...

Loading video analysis...