CppCon 2018: Jason Turner “Applied Best Practices”

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
- Hey good morning. This is Applied Best Practices. Hopefully you are in the right room. If you are just now walking in and I see you walking in, there's gonna be a slide up here telling you to move closer to the front. If you've been to any of my talks ever, you know that I like to interact with my audience. So if you're back there in the back, right now walking in, just come closer, that'd be great. All right, so my name is Jason Turner. This is just my standard about me slide. I first used C++ in about 1996 and I am host of C++ Weekly and cohost of CppCast, which is a podcast for C++ developers. C++ Weekly is my YouTube channel. Every single week I release a video on Monday that is something random about C++ and I have been a Microsoft MVP for my contributions in C++, since 2015. Just for the record I am independent available for training and contracting. And these are my URLs if you want to come back to that also. As I said, don't be afraid to move closer to the front. This is a very deep room. Can seat like 1,300 people and there's less than that in here right now. So there's plenty of free spaces. Please be sure to interrupt me and ask questions. I have asked that the lights be a little bit dimmer so that I can see everyone. And just for the record, this is approximately what my training looks like, if you do have any interest in coming to hire me to do training at your company. So I have a C++ best practices class that is a full two days. Don't think that you're going to learn everything about that two day class in this one hour thing. This is a little bit more fun, certainly way less deep than my two day class. That's coming up starting tomorrow. There's still time to sign up if you're interested. And I got a class on constexpr coming up. That's C++ on C in 2019 in Folkestone, England. Is Phil here? Well, Phil's hosting it. It don't see him. Also working on an idea for doing a special three day training of that with Matt and Charley Bay if you're interested in that. Before we move on our camera man right here, who is currently setting up, just got married last week. Let's congratulate him, whoo! And took time out to still do this before going on his honeymoon, so pretty crazy. (audience applauds) all right, so best practices and me. From 2007 to 2011 I wrote a series of blog posts on the proper usage of language things like RAII. I did not realize when I wrote those first articles in 2007, what I was getting myself into. 2015 C++ Now, I did a talk called Thinking Portable, the first conference talk that I did. I make the argument in that talk that portability as a best practice. Every C++ project should be portable across platforms, across compilers. Same time I released cppbestpractices.com. I mentioned this in one of the intro slides. This is my forkable coding standards document. It's on GitHub. Go to this URL. Basically this forwards you to GitHub. You can go to GitHub. You can fork it. You can make changes that are appropriate for organization. And then 2016 I published a learning C++ Best Practices video from O'Reilly. And again in 2016 Practical Performance Practices. And now I just say basically, here are some practices that you can do on every single line of code in C++. It will make your code faster. Arguably we're talking best practices here. Then I taught my first actual best practices class and then Practical C++ 17. This is again, best practices. How do we actually use C++ 17? And then at Pacific++ 2017 I was talking about exceptions. Where should exceptions be used appropriately. Don't be afraid of them. I started to realize at some point, this has pretty much been the complete theme of every single talk I've ever given has something to do with best practices. So basically what happens if I try to apply all the things that I've been teaching for the last 11 years, in a project? So far, most of my material's been based on my experience with ChaiScript. You may have heard it mentioned. It's a scripting engine for C++. ChaiScript was originally created with C++ 03 in 2009 with cross compiler, cross OS compatibility. I have upgraded it from C++ 03 to 11 to 14 and the current master branch using C++ 17, trying to follow my best practices. Effort was worth it. The code is more maintainable, considerably faster than it used to be. But trying to uniformly apply all of these best practices to an aging code base is very difficult. Some of my early design decisions, such as relying on shared_ptr for reference counted objects is really hard to get around. That would mean fundamentally changing the nature of my scripting engine if I did. But just a quick poll. Who thinks shared_ptr is something you should use throughout your code base? Yeah, right? So if you started a project before there was a unique pointer, before there were R value references and you used shared_ptr for keeping track of your keep allocated things, at least you didn't have memory leaks, probably. Unless you accidentally got a cyclical dependency, which I've done. But it's really difficult to move away from those design decisions. So I felt like I needed a new project. One where I could apply all of my best practices from the start, without worrying about breaking anyone's backward compatibility. This is a question I've gotten asked many times. How does one go about learning C++? I always suggest that you need a real project to work on. And I know if you don't have a job you have to pick your own project. Specifically you want a project that sounds easy. (audience laughs) This is absolute key. And it has to be something that interests you. I will dig into this sounds easy, throughout the course of this talk. Projects that sound easy almost never are. This is exactly what we want, right? We want it to sound easy so we get hooked before we know just how hard the project actually is. Okay, so I decided that I was going to create a simple ARM emulator. (audience laughs) That sounds easy, right? I'm like, well I have a cousin. Well lots of people have cousins. I have a cousin who, every time he learns a new programming language, he writes a NES emulator. And the NES 6502 processor which I've talked about in other talks, really easy and the NES hardware is not terribly complicated until you get to all of the special things that cartridges can do with mappers and whatever. So he's done that a million times. I'm like, "I'll just write an ARM emulator. "That should be easy." Well we'll talk more about that in a minute too. But just out of curiosity who watches C++Weekly? Did you see my episode on the constexpr ARM emulator? Okay, well cool. For those who are on YouTube watching this, almost no one raised their hand for the second question. Okay, so for the sake of this project I thought let's start with strongly typed things. I need strongly typed integers. And I'm thinking specifically about CPU operations. I've got Op and maybe some generic instruction. In the actual code it's instruction but it doesn't fit on the slide very well so I shortened it to Op. And then maybe some specific type of operation, something that works with the ALU. So I wanted this strongly typed so that I can have more expressive codes. So I can have a series of process functions that do the correct thing based on which type has passed in. And it looks something like this. I'm using some CRTP. Don't worry about that right now. Any questions about this? So basically something like this. I've got my strongly typed thing. It has a data number called m_data. This is whatever the value is in there. It's nt 32 t or something. Do we like this code? Any comments? It shouldn't be const? Okay, you have to yell back at me. There's a lot of people in here. If you're in the back you might have to sprint towards one of the microphones if you want to yell at me. That's why you should come closer. Why should it be const? - [Woman] Because you can't design to it. - You can't design to it. It reduces the actual real usability of this. Putting this thing in a vector for example, is possible, but then after that what are you going to do? So it plausibly reduces our usability too much. I needed an accessor. These are my two options. Should I return my accessed value by const ref or by value? Who says by value? NFO people. Who says by const ref? I feel like it's 50 50. So there's no clear winner here so I get to make the decision. Okay, I'm returning by value. This is intended for small, trivial types. There's no reason to effectively return a pointer to the thing. These are small types. Like I said, you went to 32 t. These are instructions on a 32 bit ARM emulator. So I can enforce this. I can put in a static assert. Make sure that only trivial types are used in this. Then I know that this is gonna be okay. Should my accessor be a const member function? - [Man] Yes! - Yes, thank you for yelling, yes. Is it possible for my accessor to throw an exception? - [Group] Not trivial type. - Not for trivial types. So it is no except, okay? Yes Richard? - [Richard] In a different network. - One step at a time. - [Man] Excuse me, can you give a definition of what a trivial type is? - Oh yes sorry. A trivial type is trivially copyable, trivially constructable, does it require trivially default constructable? It's a type trait. I can't remember the exact specifics it requires but it's definitely trivially copyable and trivially moveable. Effectively means we are not going to provide our own copy constructor, or move constructor, or copy assignment, or move assignment, or anything like that, and that the compiler can do the like m copy easiest thing, yes? - [Younger Man] Isn't there a type trait for nothrow copy constructor that we should use here? - I'm okay with just asserting that the entire thing is trivial here. Does anyone want to disagree with me? Because I am sure there is someone here who knows these type traits better than I do. Who is okay with saying, "Is trivial "covers our bases for this?" All right, that's like 75% of the people, we'll go with it. That's how you do code reviews at your company, right? It's just voting. (audience laughs) So can this accessor be constexpr? Yes, okay, so now it's constexpr. Does Richard have anything else he wants to say? Are you sure? Is it an error to call this function and not use the return value? Is it a logical error in your code if you call this member function and discard the return value? - [Group] Yeah. - yes, okay. So now it is a no discard constexpr no except member function. Are we okay? (audience laughs) - [Man] It's a mouth full. - It's a mouth full, yes. Who's okay with this? Like three people are okay with it. (audience laughs) There's a couple people. Okay, since I know that not everyone votes, who's not okay with this? It's about the same people who said they were okay with it. The rest of you are waiting to make a judgment I guess. These are our options for how we could do the return type here. We have our no discard const noexcept member function and we have the option of just using auto return type deduction, option a. We have option b, which is using trailing return type. And we have option c which is manually putting the return type there in the front. So option a full auto return type deduction, you vote. Okay, option b. Almost no one likes trailing return type. Who likes option c? Most people like option c, okay. Just for the record I pretty much fall in category a as far as what looks best to me. Because I'm like, do I care what the return type is? If you saw the lightening talk, if forget what it was but it was about this kind of thing. - [Man] Always Avoid Auto. - Almost Always Avoid Auto, yeah. I'm definitely not in the category that fully agrees with that. Let's make this slightly more complicated. That was a trivial example. Now we have trailing return types in group a and we have our return types, well not trailing return types in group b. Does this change your opinion? Now who says they prefer group a? Okay, who still prefers group b? I feel like I changed someone's mind. There's a more even split this time. So other people that I've talked to who generally prefer trailing return types, they basically point this out. That by the time we're in keyword soup here, it's hard to see what the name of the function is now. So this is an argument for using trailing return types. - [Man] Or syntax highlighting. - Or syntax highlighting, yeah. Well this is partially syntax highlighted, right? The keywords are highlighted but not in different colors. I've had some discussion with people that work on IDEs about the possibility of graying out attributes. So they're there but less like mentally, less mental load I guess, to look at them. Yeah, I don't know. You can definitely play with your ID settings. Make sure we're on the right slide. This is a short snippet of the actual code that has been slightly trimmed up to fit on the slide. It looks like this. I've got constexpr things. I've got no discard, on pretty much every non mutating member function. So any member function that is const and returns a value there's nodiscard in this code. Everything is noexcept. Looks something like this. I don't have any other insights or anything. I just want to just throw that up there. So constexpr. What cannot be constexpr in my ARM emulator? - [Man] Program counter. - Program counter? So the program counter, let's try to go back to that. So the pc right there, it's on line 19. I'm incrementing the pc. I'm setting up the pc to forward past the memory location that I wanna jump into in my run. Because the pc is always four bytes ahead of the instruction you're actually executing. But this is a constexpr member function that is manipulating the program counter. The program counter itself, as a member variable, no we don't want that to be constexpr, that's not static. It's not a constant, that is. But the rest of the system, all of the member functions are constexpr. So basically, there's nothing in my emulator that can't be constexpr. And if you think about it from a logical perspective you think a Nintendo emulator, or a Game Boy emulator. You know at compile time exactly how much memory it has. You know what it's video processors can do. You can pass it a RAM at compile time. You can give it anything at compile time. You just wouldn't be able to interact with it at compile time. But there's really no part of this that can't be constexpr. So what are the downsides of constexpr? Anyone wanna throw out any reason why you are not currently using constexpr in your code base? - [Man] Compile time. - Compile time, someone says. Any other comments? - [Younger Man] memory. - Memory, compiler memory you suggest. - [Younger Man] No muscle memory. - Oh, muscle memory, I'm sorry. Yes, you're just not used to typing constexpr. I highly recommend doing conference talks about constexpr and then it just flows whenever you're typing C++. Okay, so this is the real downside. Anything that's constexpr must be in a header, effectively. We must know it's definition at compile time like a template, otherwise the compiler would have no way of executing it at compile time. Constexpr though, being a header does not, notice the qualifiers? Not necessarily. Means slow compilation times. Compilation times generally speaking come from very large symbol tables. Constexpr doesn't necessarily have to contribute to this. Because we are not generating a bunch of new types. We're not doing these crazy template meta programming things with constexpr. We don't have two pulls that have to generate reams of code or anything. These are just simple functions that happen to be in a header file with the constexpr key word in front of them. Do you believe me? Well I don't have a demo for this set up. But if you go back and watch the YouTube video where I first talk about this emulator I can actually edit, I can put it in compiler explorer and I can edit it at approximately, it can compile at approximately the speed I can type, that is, for the CPU emulator itself. So it's pretty fast. But with everything being constexpr, I end up with my unit test looking like this. I actually have created a static test thing here. This is all wrapped up neatly in Phil Nash's Catch 2 framework. If you're not using that, I recommend it. But basically if my unit test's compile then I know that they succeeded. (man applauds) Thanks for the one hand clap. We have full constexpr CPU emulation here. That's not a problem. Yes, Richard. - [Richard] So actually then, we can use the CPU function file as a construct and local static? We just have systems as one. - Are you saying just even making your local static functions constexpr if you can. I mean, things that are specific to a one translation unit, not in the header file at all. And just continue with that. Yeah, I will talk a little bit more about constexpr and some of the advantages too here. Although I am moving too slowly. We're going to have to speed up. Okay, we now know provably, that we can execute any arbitrary code at compile time. If we have an ARM emulator at compile time that's Turing complete, right? So we can do anything at compile time. If anyone was in Hannah's talk on, I believe, Monday, maybe it was Tuesday, on her constexpr reg x, then you shouldn't be really surprised by this. Okay, what values return from main? Hello? - [Man] Undefined! - Undefined, okay thank you. I was just gonna wait until someone gave the correct answer. This is undefined. It is undefined to shift the same or more than the size of the thing you are shifting. And this is a 32 bit int. Well int, as far as I know is 32 bits on every platform right now. So you cannot shift by greater than or equal to the number of bits without invoking undefined behavior. Now what happens? - [Man] Compiler error. - Compiler error. You are not allowed to invoke undefined behavior in a constexpr context. So by using constexpr we can catch an extra class of undefined behavior that our compiler warnings cannot necessarily find. Different compilers have different levels of conformance with UB. And portability and testing against compilers, this goes back to my thinking portable. More compilers better. Then we can catch more classes or errors at compile time. So I have an extra level of guarantee that I have actually correctly implemented my ARM emulator. Because I know that I'm not invoking undefined behavior, basically. So what do we think now? We have our no discard constexpr const noexcept with trailing return type. Any comments? I'm looking for a specific comment. So if someone doesn't yell it quickly I'll just go to the next slide. - [Man] Defaults are bad. - The current defaults are wrong, possibly. What if we were to reverse our defaults? What if our functions looked like this instead? And it was assumed to be a constexpr const noexcept no discard function. That'd be pretty cool. - [Man] It'd be readable. - It'd be readable. We can't do that, right? I mean it would break too much code, today, to do that. But then we would reverse things around. We would have to say that it does throw an exception. It does mutate member data. And oh, by the way it's okay to discard this return value. It's interesting. Mental exercise perhaps. But this should maybe remind you of something that's currently in the language today. - [Man] Lambdas. - Lambdas. So lambdas are not noexcept or nodiscard by default but they are const and constexpr by default. It gets us halfway there. And they use auto return type by default. So if we're using a lambda we can just put the trailing return type if we need it. We don't have to have that redundant auto at the front. I'm not saying every function should become a lambda, just for the record. I just wanted to point this out. That with the newer features the standards committee is kind of switching our level our way of thinking, more towards what the defaults perhaps could have been. All right so some of those lessons that I learned, working on this. It definitely takes discipline to remember the noexcept nodiscard world. Who said muscle memory? You said muscle memory, Victor, yes. But once we get in the habit of typing it it's not that hard. If you assume constexpr, it's easy to stay in the constexpr world. But you must have constexpr text to make sure that your code is actually working in the constexpr context, yes? - [Man] Can you comment on true constexpr? Once we have that? - Oh the constexpr for Clang? No, in general will not comment on things that have not been approved by the committee yet. (he laughs) Yeah, so for the sake of the video there is a plausible feature coming up that would force something to be in constexpr or compile time. That's not relevant for this. If my emulator only worked at compile time then it's literally just a mental exercise at that point. Now we can't actually do anything fun with it. Okay, so our constexpr catches undefined behavior. In my opinion, working on this project, Clang format became a necessity. So about Clang format. It takes discipline and you need tools to keep you in line, to keep all of this stuff looking good. Did anyone see Tony Vinyard's Post Modern C++ talk? Tony basically argues that code formatters are bad because they take away expressiveness from our code. We can't use formatting to express some meaning. And I watched that talk live and I agreed with every word he said. And then I started using Clang format and now I completely disagree with him because I love it. I am able now to just type, let it flow, Clang format. Bam, everything looks nice and pretty. And I don't have to go back and think, "I have to reformat all this to make it look good." I also argue that using Clang format makes accepting patches from other people easier because you don't have to say, "Well this is great except you use tabs everywhere." You can say, "Please run Clang format "and resubmit your patch." I also found that it was very easy to become undisciplined when I was working on something I didn't understand yet. When I'm in, like, research mode. Or if I'm using a new library for the first time, then it seems like my brain, whatever part of my brain that does best practices naturally, started to shut down because I'm doing something that I don't understand right now. And then I had to go back and apply my best practices. That surprised me. Maybe it's something you can look for when you're working on your own code. If you're doing something that is unknown to you maybe the same kinds of things happen to you. So on the note of constexpr. And kind of on the note of what Richard said here, I'm like, hey everything's constexpr. And everything's in a header file basically. And then realized, wait a minute, there are some things that don't need to be in header files, right? Like I have to remind myself of that at this point. But that's okay. We can put things in our CBP files, things that rely on external libraries, code that needs dynamic memory, things that do things that can't be done at compile time. I keep mentioning constexpr. That is actually not the point of this talk, just for the record. But I found it interesting, the more I got into this that constexpr appears to be the subset of C++ that people say they want. No or minimal undefined behavior. Exception handling can't happen. No dynamic allocations. We tend to use trivial types. And with trivial types, move semantics are almost completely irrelevant. Because we don't have to care about efficiently moving something that is trivial to move. So I also learned that build system is critical. I already knew that. I was surprised by something. I'm prototyping this project. I have no warnings. I set up my build system and now I have tons of warnings that I have to go and correct. This is basically what my prototyping command line looks like. If I just got a single file, I'm typing out whatever warnings all extra shadow pedantic, those are my defaults. Does this look good to you? - [Man] W error. - The W error, well yeah okay. I could have put W error in there. Although I find that kind of just annoying in prototyping mode, personally. But that's fine. I won't argue with you on that. Would you add any other warnings to this? - [Man] Lots. - What's that? - [Man] Lots of warning. - Lot's of warnings, okay. This is my protyping command line. I mentioned that I have cppbestpractices.com? This is my recommended set of warnings on that website. When I set up my build system, I had many more warnings that I wasn't thinking about. I'm not gonna walk through all of these, don't worry about that. There's still one missing though. Anyone? Just for the record this is not the entire set of warnings that GCC supports. It's just a really good set of warnings that GCC supports. Okay, who went to Herb's talk yesterday? - [Man] Lifetime? - Yeah, missing W lifetime. So, expect that I'll be updating my docs on that. Is that what you're gonna say? So even on a trivially sized prototype project, warnings and type conversions can get out of hand if you don't start with them enabled, that's key. Topic of warnings, I did a Twitter query a while back and I asked people what feature from C++ you would remove if you could? And I got a lot of different answers but one bubbled to the top. - [Guy] Exceptions. - Exceptions is what Guy says. - [Richard] Macro. - Macros is what Richard says. What's that? - [Man] Implicit array conversion. - Implicit array conversion that's my answer. But it's related. The answer to bubble to the top was implicit conversions, in general. Implicit conversions. And I do agree with this. Now I will say from a teaching perspective anyone want to guess what warning I have the hardest time convincing people to enable on their compiler? W conversions. It causes too many warnings. Well okay, that's because we're using too many implicit conversions really. But it does make somethings painful. Like this. This is the dif. We saw my pc plus equals whatever. If I need to go, I'm executing a branch instructions, so I need to either jump forward or backward. I've got some offset. This is a signed int 32 t. And I'm doing the plus four because like I said, the pc is always four ahead of the instruction that's currently executing. The top line is the line of code that I want to write. My u int 32 t plus equals int 32 t plus four, basically. No, compiler was having nothing to do with this. I was getting, the bottom line is what I ended up implementing. Knowing, well okay I can rely two's compliment math here and I'm 99% sure I'm not doing anything that is undefined behavior at this point because I'm converting that to an unsigned int. And those bits, added back into the bits that I care about will do a subtraction if that's what needed to happen. And it works at compile time so I'm okay with it. But yeah, this is like, I left this warning for two weeks before I finally decided, "Fine, I'll write all the static casts "that I have to there to make it compile without a warning." all right, what warning might we get here? - [Man] Fall through. - Possible fall through, right? But why? We have an enum class. It has two values and we are checking for both of those values, right? We will get a warning. Not all paths return a value. So I think GCC warns and Clang doesn't or maybe it's the other way around. What do we do? I have a lot to decide about what to do with this. So let's look at this. I've mocked this up. I've got my two options. I'm handling both of the options. And we can look at compiler explorer here. And we can see on the right hand side that when the compiler compiles this it basically tests to see if the input is zero, the first value. And if it is, let me make sure I'm reading this right. It jumps down to label one. It bores out so it zeroes out EDI, which was already zero. Anyhow, then it calls the t1 handler. otherwise it calls the t2 handler, right? So this is very straightforward code. It says these are the exact two possibilities. What happens if I add a third option? So now I've got option one, option two, option three. Compiler's done the exact same thing. So if I were to pass in option three to my process function here, it's going to call option two. Because otherwise it would be undefined behavior. Because it would fall through the function and not return a value. So the only option that it has is to just, it's fine. It'll just go with the other handler. So I can demonstrate this. I am going to explicitly create an object of my types enum with the value three. Three is currently not a value that's an enumeration. The enumeration's gonna have the values zero and one in it for option one and option two. So what does the compiler do? We can look in main here. It calls the handler for t2. Let's pass the value three to the handler for t2. Because otherwise it would be undefined behavior. Is this okay? Nah, yeah I don't know. I'm not gonna argue whether or not this is a correct optimization to make but I like how clean the resulting code is, yes. - [Man] Isn't already undefined behavior? Because three is not in the enum type? - Is it not already undefined behavior because three is not in the enumeration type? That is an excellent question that we will discuss right now. (audience laughs) See the comment here. Is this illegal in any way? So I have a vote saying that this is illegal. Who says it is illegal? That's a pretty good number. Who says it is not illegal? It's 50 50 so we have to go the standard. Okay, for an enumeration whose underlying type is fixed the values of the enumeration are the values of the underlying type. What is the underlying type of my enum class? It's an int. The default underlying type of an enum class is an int. For scoped enumeration if you're gonna look it up in the standard. So the underlying type is int. So our value, our possible values are the entire set of integers. So what happens if I say, well I need to guard against this. Because I need to know if someone actually passed me an invalid op code. Our code goes from trivially simple and easy to read to something that has to actually check, did you pass in zero, or did you pass in one, or did you pass in something else and now I need to call the acert? And I have commented that this is the mysterious reappearing warning. Does anyone know why I have that here? - [Man] Acert compiler. - Acerts are compiled at release mode. If I build this in release mode I get a warning. If I build it in debug mode I don't get a warning. It's very exciting that way. I could put an abort. (audience laughs) I will say actually, at the moment this is how Fyber emulator's written because I just want it to crash if I hit an invalid op code that I'm not handling yet. But I will get back to that. I could throw, this is constexpr land, right? We can't throw. What about this? What if I call some unhandled instruction handler and then just return a default value? I honestly don't know. I'm trying to eliminate all the possible warnings from my code. And this is where I'm hitting sticking points. Yes, Richard. I'm sorry, the question was, "Can it deduce what the type is there?" That's a good question because I'm not explicitly specifying the type and on line 14 would it deduce the thing that was determined by the previous? I thought it compiled. But this might have ended up as slide ware, basically. I'd have to double check, yes? Why am I not adding a default option for the switch? Because I didn't want to add a new op code to my set of enums and not be warned that I wasn't handling all of the switch cases. So if I put a default option here then I will never ever get a warning again, saying that I'm not handling all the cases, even if I go back and change and add new cases. I don't think the default is the right answer here. I think that makes the code too hard to maintain for this example. - [Man] Stood variant. Stood variant. See, you all are gonna derail me time wise. Stood variant, I asked myself many questions about stood variant. Stood variant can be used in a constexpr context. It has limitations though. It cannot be reassigned in a constexpr context. And it actually makes sense, if you know anything about how stood variant has to be implemented. The other thing is that it depends heavily on your compiler and your standard library as to whether or not stood variant compiles to something highly efficient, like a switch statement or whatever. Or if it doesn't. I strongly considered, I might go back to variant. But for other reasons variant is hard for me to use in this code. Okay, subtle rant on my warnings back to my build system. And I have to move a little bit faster. I have 10 years of bad experience with CMake. Does anyone else have 10 years of bad experience with CMake? CMake, today, is much better than it was 10 years ago. You need to, at some point start a project from scratch and try to find and follow the best practices for CMake and you'll go, "Oh." And it's like a light bulb moment. CMake format exists. It is like Clang format. You can install it with Pip. And it is awesome for keeping your CMake code more readable. Package management. I wanted to put to use package management for the first time ever. So I knew that I relied on SFML, rang, and Catch 2. And I was also considering using spdlog and format lib. Sorry, I saw someone taking a picture. I evaluated Buckaroo, build2, Conan. Conan has Conan Center which is all of their vetted packages and Conan regular which is all the other packages. C++ Archive Network, Hunter, qpm, and vcpkg. This is my resulting chart of the different versions of the projects that were available. Across the top is the current version of each of these packages. If you look quickly you will see there is exactly one line that actually has all of the current versions of all of the packages that I wanted to use. From my perspective, Conan was the option. I considered going with vcpkg, because rang, itself, is a header only library. However, see these asterisks right here next to SFML for 2.5.0 vcpkg? That means there are several open issues with it not actually working on Windows. I'm thinking portable, so I needed something that would be able to use SFML on Windows with my package manager. So I went with Conan. While I'm working on this I had this conversation with my cousin who was recently on the Mozilla Rust team. I say, "Hey, I wrote an ELF parser for my project." Five minutes later my cousin says, "Look what I just did with the ELF crate in Rust." Okay, I said, "I did not even consider "looking for an ELF parser for C++." Like it didn't cross my mind. I've been programming in C++ for like 20 years. I'm not used to this mentality of looking first, at our package managers. So I think as we are moving forward as a community, we have to start getting in this mindset of, "Stop reinventing the wheel." Start using other packages when we can. I've only personally used package managers in Ruby and Python, where I didn't really care what the license was. It was for small internal packages. Our package managers right now, make it very easy, plausibly, too easy to add new dependencies without considering if we have a compatible license. So I think we need our package managers to start, perhaps, doing some sort of license check for us. I think that's quite doable. And if you're in Patricia's talk, making it fixable, which was earlier in the week also. She pointed out that our package manager should warn or error if we install a package with a known vulnerability. This would be a great way for us to stay on top of vulnerabilities and our dependencies, if it were done. So, as I mentioned in my previous talk by this name, basically every program I've ever written has had to run on multiple platforms. If you really care, you can go back to the dot talk but it's something like five CPU architectures across four different operating systems. I prefer working on Linux. But I want to reach a wider audience with Windows maybe Mac OS. These are huge. Trusting and using thread, filesystem, regex. Now granted, filesystem didn't come until C++ 17. The compiler support has been a little slow. But all the compiler's support it now. Thread, people complain about regex. I'm gonna ask this question again. Who was in Hannah's talk on the compile time regular expressions? Yeah, so our standard library regex. Is it outstanding? No, probably not. But it does exist. It is a good utility, use it. If you decide that you actually need something with better run time performance later that's an optimization. If you're going to use your package manager be sure to research that it actually supports the platforms that you need it to support. Okay, so who saw my Commodore 64 talk from 2016? Cool, I had a ton of fun working on that project. Just for the record, it was one of the most fun things I've ever done. But each line of C++ in that project was carefully crafted to emit the exact CPU instructions that I needed it to emit. So that it was possible for me to translate it to 6502 assembly. It was extremely fragile and a little secret is that couldn't actually handle function calls because it had no stack handling. And I had no jump, well I had jump instructions. I had no stack handling of any kind. Super fun to work on, taught me a lot. I wanted to bring this fun to other people. This is why I started working on this ARM emulator. But without these limitations and with the goal of making it a learning tool. This is my basic concept. Commodore BASIC meets C++ with Compiler Explorer. I will demonstrate. I think I will demonstrate right now. So I am calling this my C++ box. I have my static screen right here because I haven't written any code yet, it can't compile. Now I've got just a couple of learning goals. Your goal is to, that is unfortunate. Why does it say %s right there? That's what I get for making a last minute change to my code. So my string formatting right there isn't working. But I've got this goal. I want to make a simple program with a main function that compiles and produces a binary and returns zero. Can you see this? I can make it bigger if I need to. Should I make it bigger or not? Okay, that's good. Okay, so I can make my program that returns zero. It compiled it. It executed it. See we no longer have the staticky screen here because it actually ran my program and my current goal number switched from zero to one. Now this is a little sad since there's only two goals here. It's kind of hard to see this. But my second goal is now I need to write a main function that returns five. Now for this one we're going to go ahead and put this in pause mode. And we're going to look at the state of the system since this is an ARM emulator and look at our source code output here. So now I have written this so that it says return five. And we can see that we are currently paused on the first line of main. And we can look at the memory output here and see that we are getting ready to move five into our zero. This is the return value for a function returning an integer on an ARM. And we can step through this. We have our goal up here. We can step through it. The system is going to highlight what register's changed, what instruction we're currently on. We're getting ready to move the link register back into the program counter so that we're returning from main and we've returned five from it. And we can see down here on the bottom, our source code on line three, has moved down to line three that is. So I step down one. I return five from main. I got a check box. This is the ultimate goal, to make this fun to program in C++. So that's my trivial learning example. And so far that little screen on the side doesn't seem very meaningful. But we can do some simple graphics output kind of things with it. And it's all work in progress. But you can just leave it run live and pause and step through it at any moment and see really what the compiler's done with your code and what you're doing. So that's the concept. I have eight minutes. So, my project, I said at the beginning, "You want a project "that sounds simple." Ultimately my simple project had an ARM CPU emulator. This is expected. A GUI front end, expected. A basic ELF parser, not expected. This needs to be cross platform, right? I'm compiling with Clang, to ARM on Windows on my x86 box, right? So I can compile object files but I can't compile executables, so I ended up having to write a simple linker to be able to look up symbols. And I had to partially stub out the C standard library to get this to work but this is the point of a project that sounds simple. Now I learned 10 times as much as I expected to. My goal, ultimately, with this project is to kind of gameify learning best practices so you get points for using consts, using constexpr, that kind of thing. You get negative points for extra CPU instructions, extra executed, warnings generated, whatever. Limitations right now, the standard library support is limited, no FPU support yet, that's planned. No exceptions yet, no RTTI. We'll see about those. No static initialization. That's definitely fixable. So who thinks that I should implement this so that you can use exceptions and RTTI in this, learning how to program C++ tool? Like three people. Yeah, all right. What's that? Boss mode. Actually Richard, on a serious note I was considering that when you're on level zero, learning, that feature's are disabled. You can't type the template keyword. You'd get an error in the system. For example, force people to take it one step at a time. And then once you've leveled up now you're allowed to use templates. So, Guy. This was in Guy's talk earlier this week. This was his experience with getting his io2d compiling with a package manager. So I wanted to mention this. It's missing just the testing lines. This is my appveyor.yml file. I've got install Conan, set up the package database, that I can use. And then tell Conan to install whatever packages are missing. And then build a CMake. That's it. This is on Windows. So our package managers have really come a long ways, terribly impressed by this. Who said this, this week? Kate did. "Is it faster to write a simpler code? "No, no, no, no, no," I believe was her answer to herself. There's still a lot of simplifications to do. I tried really hard on this code base to make it as simple, and as readable, and as clean as possible. But it's currently at 2500 lines of code. I feel like that's pretty good for an ARM emulator, ELF parser, a simple linker, and a GUI, personally. If you wanna check it out it's on GitHub. It's Cpp Box is the name. You can check it out and compile it yourself. At the moment, paths to the compilers are hard coded. That needs to get fixed. So I want this project, itself, to be an example of best practices. So people can go back and look at it and be like, "Okay, this guy who's teaching these things, "actually is running a project "that's following all these things." Continuous integration by the way, is absolutely key and is critical and I'm sure you all know that. But I didn't have my continuous integration environment set up for the first couple months in this project. And then I really wished that I had done it from the beginning. I wanna point this out. The pain of setting up your CI helps you refine your build story. Once I set up my CI, was when I really realized that yes, my build script can in fact be just this simple. Okay, and that wraps us up. This is me. Does anyone have any questions? We have just a few minutes left. (audience applauds) Run, run. - [Man] So I wanna just highlight a slight tension between two things you said. It was a great talk and I really enjoyed it and learned a lot. It was great stuff. But at the beginning you said that the way we get lured into this stuff is that it sounds easy and it was interesting. Is that how I remember correctly? - It must sound interesting to you and sound easy to you. - [Man] Sound easy to you. And somewhat later you said, "We need to as a community learn to go find "if somebody's already invented the wheel." - Yes. - [Man] And I totally agree with you on all of that. But there is a tension there, because the problem is that we need a wheel and it sounds easy, and it's interesting. So we start writing it. And I'm wondering what's the solution to that? I'm wondering if maybe we need to train ourselves that finding the wheel that somebody's already written, sounds interesting, but we happen to know that it's almost never all that easy. So how can we make that sound easy? So we actually think of doing that rather than writing it ourselves? - That's a great question. But I would still, so there's for example, a ton of awesome graphing calculator tools out there. I feel like a graphing calculator is one of these things that I sometimes recommend to people who are asking me this. Because you're like, "Oh, I'll just make a simple calculator." But it very quickly can be something actually, quite complicated. There's the, I'm writing a tool because I want to learn something. And then the mentality of, But I am also trying to get a project done for work or whatever. So in this case I wanted to learn how to write an ARM emulator. Yes, there are other ARM emulators out there. I wanted to create this thing. I also, at some point in my life would like to make my own GUI toolkit from scratch. I didn't do that. That wasn't the point of this exercise. I used SFML and MGUI on top of it. Plus the awesome work from Alias for tying MGUI and SFML together. I stood on their shoulders for the bits that I wasn't concerned about learning on this project. - [Man] I'm totally all for that. But I'm just trying to highlight, you said, "As a community we need "to start to think in terms of using "already invented wheels." And I think one of the problems is as soon as you get the slightest bit abstract, as the kind of wheel you want, it's very hard to know what it's called. And therefore it's very hard to find out if it already exists. - That is true. - [Man] I don't know if there's a solution to that. - I don't know. And in my mind it kind of comes back to like the Java API, which has everything in it, right? And sometimes it's just a matter of reading the docs until, "Oh look, that thing actually does exist." Say, it's Google. It's browsing package databases, I guess. I don't have a good, clear answer, yes. - [Younger Man] So I wanted to got back and make a comment on your switch on the enum paradigm. - Do I need to go back there or no? - [Younger Man] Not necessarily. You can use your imagination. I wanted to tell you what we currently have in our code base and then get your comment on if that is good or if I'm missing something. So I have a, and please don't throw tomatoes, macro (he laughs) which I believe is called default assert and fall through. Which I encourage everyone to put in every switch statement. - In the switch statement or outside the switch statement? - [Younger Man] In the switch statement. And if possible, have a logical fall through that will not break your program. And basically encapsulate the assertion, logic, and whatever run time diagnostic, "If this happens in production "do something to notify somebody." If you actually hit a switch statement that you don't have code for. And it's sort of the fail safe approach in the sense that if somebody expands the enum and doesn't catch a case or the enum is being switched, it will give you a run time assert preferably in unit testing or whatnot. And it sort of addresses all of those cases in a standard paradigm. And I'm wondering is that a good approach? Am I missing something that's terrible with that? - I would have to spend time actually looking at what you're doing. I feel like it doesn't, it probably would not play nicely with constexpr because it's almost certainly gonna do something that the compiler would complain about. And I don't know. My gut is that it sounds like a big hammer approach. But I don't know if it is or not. Honestly, I would have to look at it more closely to have an educated opinion, sorry. Yes? - [Man] So going back to slide nine two, nine point two I think. It's on a switch stuff. - Oh no, everyone's gonna pick on the switch stuff. - [Man] Or was it nine two. Where are the switches on here? - I don't know. - [Man] Keep going, there we go. Why are you not using constexpr here? - Oh, slide ware, sorry. Yes, that does have constexpr in the real. That should be constexpr noexcept and other things. Oh and nodiscard yes. - [Man] And would it, if it had been constexpr would it have caught any undefined behavior in that case? - It should have. Constexpr will only catch undefined behavior. Like just slapping constexpr in front of the function won't catch undefined behavior, right? You have to actually use that function in a constexpr context, in a context that would invoke undefined behavior, for it to find the undefined behavior. To be very specific. - [Man] And if you go forward two slides or so, from there, there's something. - We're technically out of time, just for the record. - [Man] Actually was it back in the cert somewhere. And I don't think I've ever seen this paradigm of a cert with the Bang and then the comment. Go back one. Oh man, I'll never find it. That one. - Oh, you've never seen that? Okay so a const character literal is going to implicitly evaluate to true. So you invert it so it's false, yes? - [Younger Man] I was just gonna say you might be able to use built in unreachable. - I've been considering that. - [Younger Man] Or assume false on MSPC might do the same thing. I'm not sure. - Actually what I was going, what I'm considering doing is a function that uses the standard defined no return attribute instead. So then it's cross platform capable as well. All right, thanks everyone. (audience applauds)
Info
Channel: CppCon
Views: 64,805
Rating: undefined out of 5
Keywords: Jason Turner, CppCon 2018, Computer Science (Field), + C (Programming Language), Bash Films, conference video recording services, conference recording services, nationwide conference recording services, conference videography services, conference video recording, conference filming services, conference services, conference recording, event videographers, capture presentation slides, record presentation slides, event video recording, video services
Id: DHOlsEd0eDE
Channel Id: undefined
Length: 63min 18sec (3798 seconds)
Published: Tue Nov 13 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.