 10X developer, what's his name? Stuart Lynch. 10X developer, Stuart Lynch made a video series on what he called Sane C++. He walked through his code base and his code style. I'm gonna do something similar but walking through my code base. Maybe we'll call it Insane C++ because compared to his style, I'm a little bit weird. So just like him, I'm gonna open a file and then we're going to talk about different features I use in C++ that might be interesting. So the first thing you might notice is that this font is very comical. I use the comic code font. I find it readable. I like it, I enjoy it. I'm not gonna get into the history of what fonts I've used in the past but this is what I've settled on. I've been using this for about two years. And I'm using the font in Vim. This is Vim 9. And I've used Vim for 15 years. So it's more of an inertia thing. I don't have any bells and whistles, LSP server, ClangD, CLS, CCLS. So I don't have autocomplete, at least not semantic autocomplete. I don't have semantic syntax coloring. I don't have go-to definition. I use other tools for those things. Mostly using GNOME terminal. So this is running in a terminal. Usually I have two terminal tabs open. One for coding and then one for running commands. Like mostly Git commands and stuff but sometimes other things. So I'm open at this file utf8.cpp. So it's a utility file. So at the top right here, we see some code comments. Well, not code comments. Some C++ aligned comments. So it says copyright and then you're in a name and then see any of the file for copyright information. So what the free stuff, this project is GPL. The free software foundation recommends that you include a copyright notice in every file. And I found that to be useful, not for me working on the project but for me using other people's projects. Sometimes I've needed to consult some other code and maybe copy code from their project into mine. And it's really nice when I have, when I could just, I don't have, if there's no code comment, say, if there's no copyright comment saying where to find who the author is or what the license is, I gotta go find it elsewhere. And sometimes they make it easy. Sometimes they make it really hard. I found finding the license is usually easy. Usually there's a file in the project called license. Though sometimes there's a caveat where they say some files are in this license, some files are in that license. And then I'm like, well, the file I'm looking at, what license is it? If the license information is right in the file, then I can just tell. Similarly, the owner, having the owner's name or a corporation name or something is helpful just to like dot the legal aspect and give proper attribution. So licenses such as MIT license say, you need to give attribution. But if they don't, if the project doesn't include a copyright line, how am I supposed to give attribution? Including this information is helpful. Now, you notice it says see end of file. So if we jump to the end of the file, here's the full copyright stuff. Originally when I started this project, all the copyright stuff was at the top of the file, that got really annoying because you open a file and half the screen is copyright comments. So I decided to put it at the bottom of the file. I liked that approach. It does mean if I wanna see something at the bottom of the file, I have to deal with this, but it's better than nothing. This is the wording that Free Software Foundation recommends for using the GPL. So I just copied the wording. Another thing that they recommend is saying what part of, what project it's from. So if somebody copies my whole file, I can more easily find it because there's a reference to the project name. So this project is called quickly.js. So not only is it, there's attribution. There's also where the project came from. That's nice if people are copy, paste and code around. So that's the copyright comment. I put in all the files. I put it in build scripts. So if you could look at the CMakeList, it's here or, whoops, shell scripts and stuff, you put it everywhere. I have a tool that checks to make sure that it's in the right files. Oh, there's a question. You don't need to update the year? I don't know if you need to update the year. The FSF does recommend you update the year every year when you touch the file. I'm too lazy about that. If it really matters, I can go look at the Git history and figure out what year was last touched and stuff. But I'm not that, I don't care that much. At Facebook, when I worked at Facebook, they always had 2004 as the year. And then I think recently they got rid of the year entirely. So just a copyright matter or whatever. I'm not a lawyer. I'm not telling you what you should do. I'm just saying what I do. Keep that in mind. Copyright expires so now it's public domain. That's not how it works. Do I have a script that appends the license to each source file? No, but I should make one. I do have a script that detects when a license is missing. So if I delete this and run the script, says, hey, you're missing a file copyright, but it doesn't add one for you. But that would be nice if it added one for you. Maybe make a boilerplate generator where you give it a file name and it gives a CPP companion header file. Class names basically specify it. That maybe makes sense. Sounds like more of an IDE feature, but I see what you're saying, EGB. Usually the way I write code, I haven't gotten much into my workflow. I don't know if we have time, but my workflow involves writing tests. And often I will start a class in the test file and then I will kick it out when I'm done testing and I want to integrate it. I make a little sandbox where I can play around with it. And I don't have to worry about it. Include files and all that stuff. What you're suggesting the boilerplate thing wouldn't work with that style? Okay, so the second thing you see here is includes. So I put the includes at the top of the file, standard C++ practice. This is a CPP file, so I'm including a bunch of H files. And all of my code is inside of a Quiklin.js folder. Or when you're looking at includes, I know that's my stuff versus that's somebody else's stuff. In this case, all these includes are my stuff. So you see the file name, but this file is source Quiklin.js utility df8, the corresponding header is in this folder, but it's .h instead. So if I go to the folder, see there's utfcpp and utfah. I used to not keep the H and the CPP in the same file or same folder, they used to be separate, but I found it much easier if they're in the same folder. Some people tried contributing and they were confused where the files were. And so I just put them in the same place. I like the style better. So for a public library, it might be a good idea to keep them separate because there's like the includes that are public for users of the library and CPP files which aren't supposed to be used by the library, at least not directly. So, but this is an application. So I think that's a different needs. So we have namespace Quiklin.js. I put everything in Quiklin in a namespace. Some projects build multiple namespaces. So this is in the utl folder. Some people might make a Quiklin.js namespace and inside of that, a utl namespace. I think that's annoying. When you do this, now you need a bunch of, in the code that uses it, either you need to write utl everywhere like utl and code utf8 or you would need to say using namespace utl. And I just want to be able to use my code. It's all my code. I don't have symbol collisions. I don't have the same class name. I don't have two different classes name the same thing in two different files. So, and even if I did that would be confusing. So I don't use multiple level namespaces. Well, I do it in a few spots, but I'll get to that later maybe. So yeah, all my codes in a Quiklin namespace. So pretty much all files will open. After all the includes, they'll open a namespace and that's the very end of the file is the closing bracket. And when you open a namespace in C++ you can use any function in that namespace. So I have this char8, which is my type. And it is part of the Quiklin.js namespace but I don't have to write. I don't have to write Quiklin.js colon colon there because I'm in the namespace already. So I can use all my code without any namespace prefix. Nice. Then we have this line. This is a comments that describes the standard that I'm referring to. Whenever I'm referencing a standard, generally, unless, okay, so this project is a JavaScript parser. I'm not gonna cite the JavaScript standard in every line of code. But sometimes there's different standards such as the LSP standard or Unicode standard. And those aren't the whole project. They're like little pieces here and there. So I will cite the standard, like this UTF-8 module, cite the Unicode standard. And then we have a function. Function names are lower snake case. I did that because for this project I decided to follow CPP core guidelines for naming convention. And the CPP core guidelines said, use what the standard does as far as naming convention. So the standard for functions, for free functions, and for member functions, they use lower snake. For types, classes, and enums and stuff, they also use lower snake case. So I just picked that style just to try it out. Turns out I don't like it. I would much prefer a style like this where you have capital letters to denote it's a type. Because sometimes it can get hardened to C or like a padded string view here. I think that'd be nicer. Some people omit the underscores. I think if you omit the underscores then it starts to get weird when you have like UTF-8. So like, do capitalize it or not. If you capitalize it, sometimes, like if there wasn't the eight there. It's like, sort of like a run on sentence. Just a bunch of capital letters all next to each other. And like, you're supposed to know that these three are different than the R. Like it's a different word. But if you have the underscores like that then even if you don't have the eight there, it separates the word UTF from result. So I think I wanna try that style. Maybe in this project I could do a mass rename for all my classes. But for now I'm sticking to this ugly lower snake casing where methods and classes look the same and sometimes it's confusing. Often it's not. I think one thing that really helps is having highlighting. So you can notice that my editor has an orange types or things that are kind of like types. It's not perfect. Like S2D cone-cone is not highlighted here but it is here and here. That's my fault. But and it's not perfect. So like if I put a new line here then it doesn't highlight this as a type. Whereas if I did a capital letter then I could tweak my editor to say, oh, if it starts with a capital letter then highlight it as a type instead of using heuristics. Like I think the main heuristic is, is it followed by another identifier? If it is highlighted as orange. That doesn't always work. Because like for example here, there's no identifier following it. It's an asterisk. So okay, that's why. So it's relying on the fact that it's a UTF, Uint8T is just a known thing in VIM. It's like a keyword. Obviously it's not my cost but. So that's why it's orange. I don't know how this works. I think, oh, this is because I made a rule regarding whitespace. So if you have an asterisk and no whitespace on one side of the asterisk, then it thinks, oh, that's a pointer, which means the thing right before the asterisk is a type. I have a bunch of heuristics that I made for my VIM syntax highlighter thing, but it ain't perfect as you can tell. But if I made this capital, that would help a lot with the editor heuristics. It would also help with my eyes. And it would help with other people too because not everybody's using my VIM settings or has semantic highlighting. And I'm using, I don't know what standard, this was what C++ version was introduced in, but I'm using some more recent C++ features. So I just start Char32T, which can store all Unicode code points. Oh, because it's Unicode stuff, Char32T. Now there is a Char8T in C++ 17 or 20. The problem is there's not great compiler support for Char8T going back like four years. And I support compilers that are like four-ish years old because this is an open source project. I want to make it as easy to build as possible, which means even if you have an old tool chain or you're on an old OS, it should still build and run pretty well. There's a squirrel in my roof. So what Char8 is, Char8 is a type alias. So either it is, if Char8T is supported by the compiler then I use it, otherwise it's just Char. This has caused a lot of issues, mainly around casting because Char might be signed, but Char8T I think is always unsigned. That can be kind of weird. Now I could have just used Char everywhere, probably, I don't know. I need, what I need to do is benchmark with and without Char8T. So allegedly Char8T cannot, like a pointer, an array of Char8T cannot alias an array of int, for example. But array of Char can alias an array of int, well, not an array of pointer. There's like aliasing rules in C++ and compilers can do more optimizations if you're more lax. So this is more lax as far as, Char8T is more lax as far as the aliasing rules. But Char, anything can be converted to Char and Char. Yeah, anything can be, any pointer can convert to a Char pointer, you can access it. Like things like memcopy. And that means the compiler, once you get a Char pointer, the compiler can't optimize as well. So that's why I have Char8T, but I haven't measured that. I probably should, because it's a lot of programmer overhead having this Char8T thing. Back to the UTF-Mate module. So here's the function signature. First off, inside the function is another function. So sometimes I'll make helper functions. In this case it's called byte and I implement it using a lambda. And there's no capture, so it's just a helper thing. I could have written it out of line like this. Get rid of that, you know. Could have written it out of line. I don't know, I just think it's nice. If a function is very specific to one other function, I think it's nice to have a helper, not a helper, have the helper function inside. That way it can't accidentally be used outside or you don't think this is a general purpose function. You know, it's very specific to Char32T and Char8. I never use it outside this module, so Char32T is just in this file, so I don't know. Anyway, that's a helper function to do a cast and you can see how I use it a lot. And that shortens up the code compared, like I have a checked, it's a checked cast. Makes the code a lot shorter here. I use a binary literal and a ditches separator. Ditches separator is very nice. You can see I often make the mistake of like, you know, I look at this and I'm like, oh okay, the top three bits are set and everything else is zero, but no, the top three bits are not set. There's a fourth bit that's not set. So having the ditches separators helps me notice issues like that. You'll notice that this I'm just writing to an output pointer with no bounds checking. I don't know if that's a smart idea, but that's what I do. Z-Haru asks, should I ever use C pointers and C++? You mean that there's C++ pointers too. They're not like only C, but yeah, you should definitely use them. I mean, don't abuse them, be smart. They're a feature that is fundamental to the language. So you should use them. That's different than say default parameters which are not fundamental to the language. Or things like that, like constexpr, not really fundamental to the language, super helpful, but not fundamental. C++ pointer is fundamental. You should definitely use it. I see people say never to use bare pointers in modern C++. They have an opinion. I think their opinion's wrong. What would you use instead of a bare pointer? I've seen some people advocate for STD optional of STD reference wrapper of whatever. But that doesn't even do all that a pointer does because you can't do pointer arithmetic with this disaster and you can't treat it as an array. It looks hideous compared to end pointer. Now it does have some advantages like here X is initialized to a no pointer. Here X is not initialized to a no pointer. X is uninitialized. That's one advantage. But I mean, come on. Another advantage is that you can undo the optionalness and now it's a required pointer, but like it still looks hideous. Not my thing. Unique pointer, shared pointer, pretty much cover all the use cases of pointers. No, no. Pointer arithmetic with a shared pointer is annoying. Pointer arithmetic with a unique pointer is impossible. You can scroll down. I'm gonna get some ugliness. So here we have an anonymous namespace. I use this instead of static because, so an anonymous namespace is like static on each function. So this is a function here. Hard to tell because of the attribute. I don't know where the static would go with the attribute there, like there's a function. I could have written static here instead of using the namespace. The problem with the static is that it doesn't work on classes. So if I wanna help her class, can you static? And I wanna tell the compiler this is only used in this file so you can do more aggressive optimizations even without LTO. So I use an anonymous namespace for classes and for consistency, I will use an anonymous namespace for functions as well. Instead of sometimes using static, sometimes using anonymous namespace. So I have a question. Which do you prefer for this project? GCC or Klang and why? I use both. When I'm developing, I use Klang because it builds significantly faster for my development cycle. For runtime performance, I found that GCC does a better job of optimizing my code. So for release builds, I prefer GCC. However, I did some recent profiling and Klang might be better at some benchmarks. I have not investigated that much though. I'm gonna stick with GCC for a while till I have a reason to switch. So that's an anonymous namespace. We have these warning things. So in my CI builds, I have a ton of warnings enabled. Sometimes I wanna turn off the warnings. So I use these macros. Now I probably should. So the reason I have the warning disabled is there's a warning issued for always inline on some older versions of GCC. Actually, I think newer versions of GCC would also warn on this. The always inline thing is kind of weird with GCC. Even though it's in the GCC attribute. Sometimes it warrants when GCC thinks it can't inline something even though it does inline it. It will still report a warning. I don't know. So this turns that off. What I really should do is instead of sprinkling this everywhere, I should make a macro for this attribute which kind of defeats the point of the attribute. But like I would want, probably want this to happen on MSVC as well. So I should make an always inline macro. But I don't touch this code much. It's not super formative sensitive. So I'm mostly dealing with ASCII files. This code is not run for ASCII code. So it's just for the weird non-ASCII stuff. So it's not performance critical. Here it's kind of hard to see because it's split up in the multiple line. That's the clink format thing. I use clink format on this code base. Not always the best formatting decisions. But here the function is called decode UTF-8 inline and its return type is decode UTF-8 results. So this is a class. Some people would call this a struct. So if I go to the header file, there's a decode UTF-8 result. It's declared with the struct keyword, but according to the standard structs or classes, classes or classes, it's all called class. They're called class types. So I use the term class even though it's declared with struct. It doesn't have methods and stuff. But yeah, it's a little helper struct to return a bunch of different things. There was a discussion on Reddit. Somebody looked at my code and gave me pointers on how to improve the design of this struct. They identified some issues like if okay is false, then I shouldn't use code point, for example. Or there's something about size is always one, I think. No, size is either one or zero if okay is false and that can be confusing because if size is zero, you might think if you fail to decode, size tells you how much to skip, but that doesn't always tell you that because sometimes it's zero. There's some design issues with this code. And that's fine as long as the code works, you know. It can trip up new users of this code or when you were factor users of the code might miss some gotcha. So a better design would help, but it works for now. So yeah, we're turning that struct. Here's another helper function. This is also very specific to this decoding code asking if it's continuation byte. Here we're treating the data as UN8Ts. Now this is what I was talking about earlier, which RAT, so pad of string view, this class is basically an array of char8s, which means for compilers that support char8t, it's unsigned, but compilers that don't support char8t, it could be signed or unsigned, but we're doing a bunch of bit math and stuff. So like this comparison, if I use char8s or char here, this comparison might always be true because char's range would be negative 80 hex. To positive 7f hex. To avoid all that stuff, I force it to be unsigned with this cast. Now if I can guarantee a newer compiler and char8t support, then I can clean that up a bit. But you know, people plus ain't perfect. So here just doing a bunch of checks for different code sequences. We're using a designated initializer here to return the structs all on one go. So another way you would do this, this is a C++ 20 feature, but it is supported by compilers for a long time. At least the GNU compilers have supported this syntax for a long time because C's has a syntax. So you could write the code like this, and then it would be standard C++ 17. But I like doing this style because if I forget one of these initializers, then a compiler will tell me, hey, there's a missing initializer here. Now some people like that feature of the compiler warning about missing initializers. And other people say the whole point of this is that you can omit things and it'll be zeroed out. I can understand both use cases. I've dealt with some lower level Linux APIs where designated initializers are the way to go. Anyone has zero in it, everything that you didn't specify because they're like 50 fields. But in my code base, if I do a designated initializer, I mostly want to have everything specified. So I want the compiler to tell me if I'm missing something. I don't think it matters too much in this case, especially because this code is pretty well tested. Here's another thing I do commonly. Well, there's static assert. I use that not too much, but it's a super handy feature. Did I also have, yeah, there was also back here. Back here was a QLJS assert, I glossed over this. So this is kind of like the standard assert macro. But one, it's capitalized, so you know what's a macro. And two, I can customize the format. And one thing that's kind of important is, let's say, let's say we make it fail. When the assertion fails, says internal check failed in the function name, and then there's the message. So first off, I get to customize the assertion message. It's not up to the standard, it's not up to the standard library implementation, what it looks like. I can make it consistent across platforms. So that's nice. Also, I produce this message saying, quickly JS crash, please report this bug here. And then I have a link to where users can report the crash. And also, you see it says illegal instruction, core nums. So if you run this code in a debugger, see where it stops. Oh, I don't have debug and fill. Have to rebuild the debug and fill. Please wait. So rerun. When in the debugger an assertion fails, you see it brings you right to the assertion. If I used see assert and do the same thing. Well, first off, you see the format's different. But if I rerun it in the debugger, the message tells me where it crashed. But the debugger doesn't stop in the right spot. I'm in pthread kill implementation. You have to go up the stack a bunch and then you get to the code. So the way I did that was in my assertion macro. This is for Windows. This is for Linux Mac. And this is, if we can't detect it correctly. So what see assert does is it calls std abort. You can see in the stack trace, assert fail is calling abort. I don't call abort. I call built-in trap. And what built-in trap does, it adds, let me show the code again. Kill JS assert. So if we disassemble this, disassemble. You see here's the call to print the things. So it says report assertion failure. And then there's this instruction, ud2. So on Intel, ud2 is the crash instruction. Built-in trap will generate this instruction. And that causes the debugger to stop right here. Not in some function somewhere it stops right here. Also it's very, very lean. It's not like 50 instructions of random stuff. It's just one two byte instruction. It's very compact. Aside from the cost of the if and calling this thing assert is pretty small in terms of code size. Well, my assert is compared to the standard assert which is a bit more. Well, actually, I think standard assert is actually, it just is without this instruction. This would be a jump. But I think the debugability is worth it. Having it jump right to the assertion message. And then you can immediately print variables of stuff. And on, so that's on Linux and on Mac with MSVC is a debug break which gives you the same thing in the Microsoft Debugger. So I wanted to point out that I'm making some Boolean variables. Now I could have crammed these conditions inside the if like this. And I think this reads okay. But I think this is better. Whoops. Where the if just says are the bytes okay? And then the details of what the bytes okay mean is right here. I use this style a lot where I name my conditions even if it was just one thing. I will sometimes, well often, write in this style. But I will name the condition and then do if the condition. This is just an example of that. This is quite unfortunate. Some of this is, so I mentioned I use clang format and sometimes it does a bad job formatting things. Or probably could have been written better like this. You could line up the conditions. See now it's more clear. There's a condition and then the result condition and then result and then the fallback. Maybe I should tell clang format to stop. Clang format off, clang format off. One problem with telling clang format to stop is you have these ugly comments which distracts from the code. Maybe that ugliness is fine. That's a problem with automated code for matters. Sometimes they're stupid. But here's another example where I do the byte okay thing. I think one of these cases, oh yeah, so here I have the byte two okay. I can reuse the byte, oh sorry, byte one okay. I can reuse that in the failure case to indicate how much did we successfully decode. If you were able to decode byte one and byte zero, this code assumes byte zero is decoded, then you can skip two bytes. Otherwise if byte one okay is false, you can only skip one. I may have written that a different way before but I settled on this. So there's multiple purposes for having these Boolean variables. Here we have a narrow cast. So narrow cast is like a checked static cast. So character count is an int and we wanna get it to be size t. I don't know why but I guess for the comparison. So the compiler warns that this comparison between a size t and an int if you omit the cast. So we got a cast to shut the compiler up. So I have this narrow cast thing which is a checked cast. So it just does a runtime comparison. It doesn't always do a runtime comparison. So if the size, if it happens that size t is, I mean it's not gonna be short. Let's say size t is 16 bytes and ints is 32 bytes or something. Then this, oh no, no, no, no, no, wait. No, okay, if you're converting from sign to unsigned, that would always do a check because it might be negative. But in other cases, narrow cast might optimize to nothing. No check, just a conversion. Where often it will be a runtime check. So basically anytime I get a conversion error, conversion compiler warning, I'll throw in a narrow cast because I'd rather be told when something is broken rather than it being silently converted to garbage numbers, like four billion, whatever. How is a narrow cast implemented? That's a good question. Let's see. So if you ignore this debug stuff for a minute, we check if we're in range. And if we're not in range, then we report an error. So then the question is, what does in range do? In range, there is a standard in range function that I could have used. Unfortunately, I'm an older compilers and older compilers don't have standard in range. So I, you made my own. So it's just this algorithm. I use a bunch of if const experts and that's the thing which like not checked at runtime if it doesn't need to be. So for example, if you're casting from an inch to an inch, those types are the same, the in and out types are the same. So we could just say it's always in range. Let's say in is a U8 and out is a U64. I don't have code explicitly to say that's always in range. Instead, I just rely on compiler optimizations signed equals signed. Yeah, that would be this case. I just rely on the compiler to optimize it, which I didn't check if it always does it, but it should check that it always does it, always optimizes. Anyway, it's just a few cases. This took a while to implement correctly cause rules of C++ conversions and stuff. And it would be nice to have a safe client library to do that for me. However, those things are bloated template messes. So I mean, this is kind of a bloated template mess, but I have not seen any pile time or runtime issues with in range. Now there is this debug stuff. What is this? So this is kind of like my assert macro thing, but a bit smarter. So let's bring that back and let's say narrow cast, let's say this was native, let's say I put negative. So it's a negative character count. That won't fit in a size T. So if we build, then we get a number not in range message and this line points to right here. So the error message that gets printed shows the call to narrow cast. It doesn't show the internals of narrow cast. So if you look at the internals of narrow cast, this is for if the compilers don't support the feature I'm about to explain. So if you don't have a supported compiler, then it'll fall back to kind of like just an assert inside of a function. So the error message would point here inside of narrow cast H. But if you do have a compiler that supports standard source location, then instead we'll use this thing. Now this is like a new C++ 20 feature where if you use standard source location and you use it as a, in the default initializer of a parameter, then the source location is for the caller, not this function. So the way source location works is internally, when you say currents, you get, it does this built it, well on GCC and Clang, it's built in file, built in function, built in line. So if we just look at line and if we wrote, so let me kill all this debug stuff just so it's clear. So instead of saying caller, if we said line is built in line, then this line variable isn't initialized to line 14. Of narrow cast H. This line variable would be, for this case, it'd be line 147. And same with the file. It's of the caller, not the callee, for the specific case of a default function parameter thing. So I take advantage of that. And I don't want, in release builds, I don't want the overhead of passing two pointers and an int. Every time I call an arrow cast, so I turn off this feature in release builds. So it's only debugging that I get good line stuff. What is the comma? This comma is supposed to be there. But if I write the comma there, then if this code gets deleted by the preprocessor, then I have a training comma. C++ doesn't like the training comma. So to avoid that issue, I have to write the comma first. That's goofy, but that's C++ for you. Moving on. I have this comment says to do, you can see it's highlighted, because Vim by default highlights the word to do. And once I discovered that, I started using to do to indicate things. Now there's this parenthesis thing. I didn't used to do that 15 years ago. I used to do comments like this. But then I joined a company. And at the company, they had the convention of putting the person's name in parentheses. I found that helpful to figure out who to ask for questions. Now this information is stored in source control. So it's not actually, there are alternatives, even if you don't have the name, there are alternatives. But another use case I've found for this kind of thing is you can put like a GitHub issue number there, or like, you know, JIRA ticket number, whatever task system you use, and that's super handy. I may have a feature in mind. I'm writing some code. I'm not ready to implement that feature, but I know I'm pretty sure that I will need to change this piece of code in order to add that feature. So I'll put a comment saying to do, and then the task number for that feature. And then maybe I'll put a message explaining stuff. Let me see if I have examples of that. Well, here's an example with a task number, except parse the options from a config or CLI options. So this is like, if somebody wants to work on this feature, they can just look for the task number, and then they'll find this code. And they'll like, no, oh, this is the code they were talking about. Now I would like to also include a link. In the task, I would want to include a link to the code, but the problem is the code often moves around, and maybe it doesn't remain up to date, whereas a comment is more likely to be up to date. This is kind of like a bookmark in the code you can reference later. So this is a performance thing I should do. I make a copy of the string, which is quite dumb. But interestingly, this has never been a performance issue that I've seen. Maybe my benchmarks aren't comprehensive enough. But this is, I mean, it's a rare case. This is only called when you have a line in your code that has non-ASCII. And we will only run this code on that when you modify that one line. That doesn't happen often. So what's my preferred max line width? I don't have a preference. I'm used to, I used to use 60. And the reason I used 60 was on the particular machine I was using is a laptop. When I did a vertical split like this, I had 60 columns on the left and 60 columns on the right. It's not the case right now. So that was nice. Because I could have all my code, like there was no weird wrapping thing. Now I use 80 because that's just the clang format default for Google style. I just picked the Google style, whatever. So the clang format default for Google is 80 columns. So I just use 80. I'm not too picky about it. Like in the Rust port of this code base, it was I think a hundred is Rust formats column width. So I just did a hundred, no problem. At the current font size, at the current resolution, this line here is 80 columns. So if I do, that's 80 Xs. So actually that column is 81. So I have room for 80, 83 Xs. On this terminal. Yeah, that's probably small for most people. I don't see too much of an issue with it. Physically, the font is huge, like to my eyes. For you, it's small because you may have noticed this viewport is not like a widescreen. This is a nine by eight monitor. Nine, eight aspect ratio monitor. It's, you can imagine it's two 1440p displays. You know, like a normal 1440p display, 69. Put two of those together and then turn it. That's this screen. So much more square. I'm happy to use this font size. I did that last night, but it's harder for y'all to see. So I'm fine with this. Hard limit 80, no, I don't do hard limits. I just let the code for matter do it. And if the code for matter does something very stupid, I'll just turn it off. I could, we could say ripgrap. Let's look for 81 things in search or test. So here's an example. This is a data table. So I turn off formatting to get the alignment. And I exceed, there's 81 columns. I'll exceed it occasionally. Here's another data table where I'm going beyond 80. So I mean, for this kind of case, let's find a scroll, I guess, especially cause it still fits on an 80 wide screen. It's just, there's a bunch of blank space at the beginning. But if it's normal code, I would, I would word rapid, but like, it's not a hard rule or anything. It's calm, it's important nowadays. I use 120. I don't think it's super important, aside from preventing the code from just getting hell along with the automatic code for matter. You know, like this line, I mean, that's 161 columns. That's ridiculous to have it all online. And I think this looks better when it's wrapped. I think that's often the case. Things just look a little better when you align it. 120 is good at pretty much every dev has ultra-wide monitor now a day. Not me, not a lot of people I know. Most people I think are just on normal wide, just 16, 9, 16, 10 monitors. I certainly am not on 16, 9, or wider. Any other thing else in this file I should comment on? So one thing you'll notice is that I always put the types. There was one exception where I use auto. Well, actually. So lamb does helper functions, I'll use auto. And apparently here I wrote auto. I don't like that, decode UTF-8 as well. I've been trying to not use auto. Mostly when I'm lazy I'll use auto. But I want to refactor away from that. So now that's the only auto in this file. See, I'll put the types I find that helps a lot. I'm also using the classic style of initialization. There's so many ways to initialize things now. If you can do auto, the problem with this is you don't know what size T, I want to say that count to size T. So what you could do is put size T of zero there. But then like here, if you did that style here you would just say auto stop. So there's no type. So now sometimes you put the type, sometimes you don't. Some people do curly style. I tried this for a while, I just hated it. Now for, this is for primitive types. I'm not completely consistent with my initialization. So this is for primitive types. And like if I'm calling a function I'll use equals two. But what's a module that has more complex types? Oh, like this guy. So we're constructing a new padded string. We're not taking an existing padded string and copying it. We have a string view thing and we're copying the data into a new padded string. So we're keep allocating here. So here I have, I'm using the parentheses style. Now, if I tried using equals, this won't compile. This would work if it was std string, but it won't compile for me. Whoops. I broke something over here. So this doesn't compile. It says no conversion from string view to padded string. And the reason is the constructor that takes a string view is marked explicit. So I will mark all my classes constructors with explicit except for, well here they're deleted, except for things like move constructors or yeah, I think that's pretty much it. Now, sometimes I will have a constructor that I wanna mark as implicit. So this is a default constructor. And normally I would write explicit. Hold on, let me put this back. So normally I would write explicit here on this constructor, but I'm using initializers. And here's a case where I do want the zero initialization stuff that I talked about earlier. For this class in particular, I want the default initialization. And if you do that, and your constructors mark explicit, the compiler would say no. You set explicit, you're supposed to initialize that explicitly like this, and it's complained about a bunch of other cases. But you set explicit so you better put the thing there. Cause this is an array that contains two things. So we put one there explicit. So that's this constructor call. This is also marked implicit. I don't think I need that, maybe I do. Am I getting this wrong? If I write explicit there. Oh yeah, so I think you need to put the type name or to not complain. Yeah, you need to put the type name. Otherwise it complains. So in the interest of shorter syntax and not having all these empty things, I made this implicit and this implicit. But most of my classes, all the constructors are explicit. And this is an issue I had with the Rust code actually. So when I ported this code to Rust, as an experiment, code like this, I had trouble with. In the Rust code, I had to explicitly write out all the things. Let me dig that up. So this is the same code, but ported to Rust. And I had to write the empties explicitly and I had to write all this stuff. If we create them side by side. So we have this code versus this code. Let's put the whole call there. You can see the C++ code. Like I saw it from formatting differences. This code is a bit more compact. I mean, maybe you could argue legibility suffers because I don't know that this is a diagnostic message or a info, but the way claim format formats things, it would format it poorly. But anyway, we have to put these empties here in the Rust code because the array, if you say you want an array of three things, you'd better provide three things. It won't zero fill for you. Maybe there's a way to do that in Rust, I don't know. Couldn't I dot, dot default defaults? I could, but that's still would be syntax noise. But that would, any bear, I guess you're saying I could do something like this. I have not seen that syntax, but it's still more verbose than I would want. I try to avoid parentheses syntax for initialize because the vexing thing. I, where was that UTF-8? For this, so you're referring to the most vexing parse thing. I've never run into that. Or if I have, I've easily worked around it. Compilers are decent nowadays at telling you about the vexing parse issue. So I just haven't run into it. I don't even remember how you're supposed to hit the issue. Is it if this thing looks like a type or something? I don't know, I don't hit that issue. You ran into most vexing parse in MSVC air message is the worst, you avoid it all together. Clang seems to do a good job. So I mostly develop with clang. Maybe you could try clang cl, if you're on Windows. Isn't the most vexing parse when you use empty parens? Well, that's not vexing. I mean, that is an issue where, oh, it even warns. Oh, it says vexing parse. That's an issue, but there's a case where if you put stuff there, it still thinks it's a function declaration, not a variable declaration. I don't remember the case though. But you can see clang warned me on it. So I guess let's look at the header file now for utf8, utf8.h. So just like in the C++ files, we have the copper header at the top and then the full stuff on the bottom. And we have an include guard. I use include guards because if there's a standard way to do things and there's no downside really, then I'll use standard way. So I have a standard include guards. Now my include guard style has evolved over the years. What I used to do was project name and then like some hash thing, like a GUID or something. I used to do a GUID. I mean, it didn't look this stupid. But GUIDs are weird and dumb and like, what the heck does it mean? So I just did, basically, this is in the include guards. This matches the header file, the include path. So you would include it by saying pound include this thing. And that's what it right here. Just all caps instead of hyphens or slashes. I use underscores, but you know, this has run in, I have run into issues with this. No, nevermind. I'm not. One thing I have is if I say foo here. It'll work because I don't have a macro called foo. But I can run my formatter tool thingy. I says, oh, the file changed. If I load it, you see it, my formatter added, fixed my include guards for me. So that's not clang format. That's a custom script I wrote. But I have screwed this up enough times that I just wrote a script to fix it up for me. Script isn't perfect. It'd be nice if there was an open source tool for it. Maybe there is. Thought I could just use, well, how about the open source tools as you have to install them. It'd be nice if it was part of clang format, which is a tool I already use. Clang format nine, which I use now, doesn't have this feature to add include guards or fix them up for you. But maybe a newer version does. That'd be nice. You tried to use include guards. Your folder structure is too deep and it got way too long. Well, the include guards, who cares how long they are? I mean, you don't see them. They're like, I don't know. I mean, you see them at the very top of the file, but like you just quickly skip past it. So who cares? I would more be worried about big paths for your pound includes, not your include guards. Yeah, if you have long paths, maybe you should stop organizing things so much. I used to not have these folders. So it used to be like this, where I had quicklinjs slash and then my stuff. No folder organization. But then that got unwieldy. So I have 250 files, source files, CPP and H. So that got out of hand eventually. I mean, I think I could deal with it, but contributors get scared when they see a folder with 200 file in it. So I split it up into separate folders. You see, here's the folders. Now, not everything's in folders. I probably should put all this stuff in folders. I just haven't done it. But you know, I got a bunch of folders and each folder has a bunch of things. But the folders are just a folder structure. It's not a namespace structure. I have cyclic dependencies between these things. They're not separate libraries just for helping organize. It's not, it doesn't affect runtime at all. We have includes app inside the include guard. So include guard, it covers all the lines of code. So inside of that is includes for the header stuff. And here we see a standard include. So I, for standards, so there's two ways to include this. There's the def H and C stood def. I prefer the C stood def one because that's just the C plus plus C way to do it. And I like to prefix the things with stood colon colon because I hate myself or something. Anything not built in, I will put a namespace. So either, okay. So if you don't see a prefix in front of something then either it's my code or it is built into C plus plus. And if it's built into C plus plus, you'd better know it. There are some exceptions like where, like this guy V sure Q and U8 and stuff. These are from the neon, our neon intrinsics. These are actually macros. So you can't namespace them. But for things in the global namespace that are functions or variables, whatever, I will do the colon colon to say, this is not my code and this is not built into C plus plus. This is from the standard, not standard. This is from the neon library. See here too for types. But I can't unfortunately do that for macros. So, and they didn't indicate that it's a macro with all caps. Which is dumb. So stuck with that. Oh, here's a little formatting thing. These should line up. That's nice. See here, I wanted everything to line up. And I also violated the 80 column limit thing. But I wanted to make them line up to make it easier to follow. I lined this, this used to be like this. So anyway, every time you look at code you might discover some little tweak you can make to make it better. So I like to put the namespace. And if it's a C library, I'll put just colon colon. Like in this case, that's just how we do it. And the way, you need to include it with the C to def. Otherwise, it might not have the standard namespace stuff. I know there was the whole iostream.h in iostream. Is there no stdef? Um, so you're asking, so this is the C version. So the C one came first. Then there's the C++ standard one. And then why not this one? Uh, I don't know. Well, that's not for me to decide, you know. I can't do anything about that. I don't know the history of the header names. I'm guessing they wanted to indicate that it was a C include somehow and they didn't want to touch the original. So doing this is off limits for adding stuff. So I guess they just added a prefix. Maybe a same thing would have been like to standardize HPP. Then you don't have this weird C prefix thing, but I ship a sale. Now some people like to group their includes. They might put all the standard ones first and then your your projects after or maybe put your projects first and the standard ones after maybe have like the standard ones and the third party libraries and then your things or whatever. I don't group them. I just sort the whole block. And I have clink format do it. Now, if you have an if around some code like here, then I'll put that in a separate paragraph like I'll put a blank line. But normally we don't have conditional clues. So let's find something like this guy. Well, that's actually not including much other stuff, but we have some standard includes up here and it's a standard include down here. It's just sorted alphabetically. So C O Q for all my stuff. And then you, so you comes after Q. So it's sorted this way. No grouping. I don't see the pointing grouping. I want the includes to be as out of the way as possible, which is hard as C plus plus. So I don't want like to have to do extra maintenance or extra work when I'm dealing with includes. I don't want to have to make sure I put blank lines in the right spots. I just want to sort it and be done with it. And the sorting is just so if I add an include, I know where to put it. I don't have to think, oh, should I group it with all the quick link things or I don't have to think about it. I just sort it. I even have an editor thing. So if I shuffle all these, I just hit a thing on my editor and it sorts it for me. Now, if a contributor comes along and doesn't have a keyword shortcut, whatever, then if they run the code format or a clang format, we'll reorder them. So sometimes I sort of manually manually, like sometimes I use my keyboard shortcuts sort of sometimes I use clang format. I'm not very consistent about that. Oh, so one thing I didn't mention is why do I use this style? So there's a bunch of different styles. So I'm in the util folder. So actually let me look at the CPP file. So we're in the util folder. So what I could have done is just include UTF-8 instead of this or include narrow cast instead of all that. That makes it hard to move code around because whenever I move code around, I can't just copy paste the includes that go with it. I have to fix up the includes. I don't like that style. I do think putting the full Quiklin.js in front is verbose. I have thought of abbreviating this to Kula.js and also doing that for this namespace. I don't know. That looks weird to me now that I put it like that. Maybe I'm just not used to it. And then obviously I would change the folder name. As I said earlier, I put everything inside the Quiklin.js namespace. So once I include stuff, namespace, everything is in here. Now some files don't have this if they just have macros. So like the assert, well actually the assert does have it. So if I have macros, you see there's no namespace declaration here. I just go right into define. So defines macros are not inside of a namespace. So I don't put them inside of a namespace. Because if you put namespace, whatever, it doesn't do anything because it's a macro. So to avoid misleading people into thinking the macros inside the namespace, I just don't try not to put my macros in a namespace. So all this macro stuff is before a namespace Quiklin.js. And then this one function, port assertion failure is inside of a namespace. So yeah, here's the UTF-8 header file, pretty small. Just a few functions, the type for the decode thing. As far as blank lines go, I don't know why the blank lines are organized this way. Just kind of arbitrary. I don't think it looks good right here. Maybe a better way to would be encode and decode, whoops. Go together and then these LSP character things, I guess go together and then it's like account separate. Maybe the account goes here, I don't know. Some people like, what's his name, Stuart Lynch? He likes to keep them all separate like this. I think this looks, it's just too much negative space for me to handle. I prefer something like this, but claim format is too wide. It's beyond 80 columns. So claim format, it's a line break there, which I'm willing to accept, I guess. Maybe something easier in my eyes would be this style, where there's not as much, it doesn't look like here. It sort of looks like there's a blank line until you notice over here. Where here doesn't look like a blank line. I don't know, that doesn't bother me too much. But there's blank lines every line. That's just like, my eyes go sideways. How to manage include header to improve compile time? When to use forward declare, should I avoid forward declare and only use them for cyclic include or should I use them, everything when possible? I tend to forward declare when possible. I mean, here I'm not. I think I could forward declare pad of string view, right? I think I could forward declare this. I would forward declare this, but this isn't a type. This is a type alias, so that gets annoying. I have thought of implementing my own though, my own string view just for compile time purposes. Because the string view templates are, the standard ones are slow, like everything in the state of library. If you notice a problem, then forwarded declaring can really help. Like, there was a library, there is a library I'm using called simdjson. Like for example, this file, I'm using this simdjson thing. I'm using some of the templates and stuff for some helper functions. They're not using any of the types. I'm just referencing the types. So I could forward declare it. So that's what I do. I made a forward declaration header, which is kind of like iOS, FWD. And I kind of did a crappy forward declaration. Their thing is complicated because they use like inline namespaces and stuff. Well, namespaces and aliases and stuff. And the logic is kind of complicated. So this actually isn't being used on x86. For x86, I just pound include the header. But for ARM, there's this implementation of simdjson, simple enough that I could do the forward declarations, right? So I did them ARM and it did improve compile times quite a bit, especially without pre-compiled headers. With pre-compiled headers, I think I include this in my pre-compiled headers set just because this header sucks. But I would like to kick that out. Because it's only a few files using it, using simdjson. But yeah, when you notice a compile time issue, you should forward declare, just know if you're gonna forward declare third party things, that can be fragile. I have considered having the simdjson team, like contributing a patch of simdjson to add simdjson forward to their headers. So anybody can use their forward declarations. That would be nice, kind of like iOS, FWD, but I haven't done that yet. I should do that, help everybody with compile times. There's one thing I wanted to point out. So we looked at some code, let me undo all that, undo. We looked at some code for in range. And I mentioned that there's a standard in range, see plus plus 20 I think. So often I will encounter something where I would like to use a standard library version of something, but it's not available. So I gotta make my own. Or maybe it is available, but its performance sucks or something, though there's a bug. I need to work around. I've needed to work around bugs. And standard library things before. Either performance bugs or like actual functionality bugs. So I will try to keep the same interface as the standard version of that function. I'll even keep the same name, which is easy because I have the same naming convention. I'll just make my own version. And I'll put most of those things in the port file. Here's an example, so numeric limits. There's a standard numeric limits. But in some compilers there's a bug in numeric limits of all things. So I have my own limits.h. I also have my own type trait set h. Oh yeah, make unsigned also has a bug in some compilers. So I wrote my own limits h and type traits h. Work around older versions of libc++ not supporting standard numeric hrat despite corresponding everyday app. So yeah, there's a bug where it doesn't work with charat, so I wrote my own numeric limits. I didn't write my full implementation. I leveraged the standard implementation and just worked around the bug with a specialization in this case. I think it's the same with type traits. Yeah, I just used the standard make unsigned and then added a workaround. So I do this in a bunch of spots when you see this port folder has a lot of stuff in it. Now it's not, the port folder is an only standard things. I have like, oh, there's that force inline attribute I should use that I was mentioning at the beginning of the stream. So I have like portability across different compilers kind of things, not just standard library stuff. Or like consteval is like a language feature in C++ 20, but if you don't have it constexpr works just fine for my use case. So market constexpr. Or like some SIMD stuff, I have some SIMD wrappers or process stuff, dealing with process APIs. That is, I put that in port as well. So port isn't just standard library stuff but I do have some standard library stuff. Like SD thread for example, wrote my own SD thread that works the same as the standard one, except it's mine. Now the reason I wrote my own standard thread is binary size. So I care about binary size for my program. Maybe I can talk about some of the tricks I do later. Another day. But when porting to Windows, I used GCC on Windows as I mentioned earlier performance. This is the reason I like GCC. And one issue with GCC and MinGW is that their standard thread uses pthread and their pthread library is big. Once you pull one thing out of it, like the standard thread or the standard mutex, you know, it pulls in the pthread thread and pthread and mutex and that pulls in everything else. You get all their thread local storage wrappers and pthread at fork or whatever. I don't know what stuff they have. So I didn't want all that because that really bloated binary size. It wasn't just that. I think the pthread used some other things and that contributed to binary bloat. So profiling of binary told me that if I got rid of pthread, my binary size would be cut down by, I don't know how much. Actually, whoops, what? Let's see if I wrote down how much it improved. See one problem with renaming of files is it kind of breaks Git log. I think there's a Git log follow to fix them. Oh look, there's a typo and a commit message, bill size. So implement our own standard thread on Windows. So we got a bill size changes. Implement our own standard thread, mutex and conditionable classes. You can reduce your bloat by a bit. But this data doesn't show that it reduced the size at all. See it reduced by like a kilobyte, two kilobytes. Am I stupid? Maybe I copy paste the wrong data or maybe I was wrong. Oh, okay. So there's actually, there were two bugs that were causing binary bloat. One of them has stood file system path and another was pthread. If I got rid of both, then I got a huge size reduction, 38%. But I think one or the other didn't reduce it. I had to get both. So that's kind of misleading. So yeah, I wrote my own. And this isn't anything fancy. This is just stood threads interface over. Well, I do mutex, condition variable and stuff too. But like stood thread is on Windows, it's wait for a single object for thread join. Starting a thread is begin 3DX. You know, the normal APIs, there's nothing, there's no logic really. There's just a few assertions and, you know, handled management and just a convenient wrapper. I mean, looking here, you see, I do use if defs for different OS stuff. Most of the time, if I have platform-specific code, I'll just leave it all in file. But if it gets unwieldy, if it's a lot of code or if there's a lot of conditional includes, I mean, here's there's quite a bit of a conditional includes, but if there's a lot, then like this file is only 250 lines long. So if I split it up into the POSIX and the Windows, I mean, they're like 120 lines each. That's not really a win. It's not drastically different sizes. So I'll keep it all in the same file. Sometimes I'll wrap the if around the entire function. And I'll do that usually if I have a bunch of functions that need to be wrapped, a conditional. And sometimes I'll put the ifs inside the function. I'm not really consistent about which way I pick. I don't have a standard there. It's just whatever I feel at the time. But yeah, this is just ugly portability stuff. Some other files like config loader. No, change the technicals, okay, not config. So I have three files here. Each of these is platform specific because these were, you know, 300 lines, 300 lines, 250 lines. So these were like big each on their own. So if I put them all in one file, it'd be annoying. Also the data structures used are very different. Like the class members are very different. So I just thought the code isn't, it's not like the functions are gonna be very similar between the three platforms. So you can like look between them. No, they're just drastically different implementations here. So they're separate files. I think this is the only case where I make separate files though. But one thing I do is I will allow you to compile all the files on all platforms. So this is iNotifyCVP. This would only build on Linux. But we compile it anyway. Or you can compile it on Windows, for example. Reason I do that is to keep the build system simple. So I could just add this if in the file, one that indicates to you as the reader, hey, this is platform specific. But it also means the build system could just list all the files. I don't have to write some complicated, oh, if your windows do this, if you're, whatever do that kind of logic in the build system, I just list the files. Gives it simpler. I'd rather write C++ code than CMake code. Then I write my own SED file system path. Good question. So regarding this commit, what did I do? Okay, so here I was using file system path as a hacky way to convert from UTF-16 to UTF-8. So I just wrote a proper function for that job. Here same thing. Yeah, that's what I was using it for. Maybe in the past I was using it for other things that I cleaned up. I would like to use this file system. Okay, the only reason, okay. The reason I used file system was because on Windows it's available with compilers that are several years old. On Linux and Mac, that's not the case. So I only used SED file system in Windows specific code paths. So that was stuff like here, this is the Windows path canonicalizer. So it's specific to Windows paths. So I could use SED file system because it was available. And I think originally this code did use SED file system more, but then I was like, got rid of that. So it was just like this piece. I also used it for like a recursive delete of a folder or whatever. Oh, here's another reason I don't use SED file system. It was buggy on GCC-8. That was annoying to fix. So like I will use SED file system if we don't have a better option. So well, in this case, I don't think we need this code path. So I used to not have this Windows code path for making a tempter. So it used to be just here's the Unixy way to make a temp directory. And then I have the standard file system way. But then I don't know why, but I added a Windows version of the specific code. And then I don't need this SED file system version anymore. You know, any old code basically have dead code. Here's another example, creating a directory. I had a reason for, let's see, Windows file creator to color detect already exists. Oh, I needed better air handling and the standard file system didn't give me good air codes. I need to detect. If you try to create a directory, but it already exists, I need to know if it's a directory out exist error. So that's why I wrote my custom code path. I could just use SED file system. SED file system, I don't like. There's too many problems with it. There's binary bloke problems. It was crashy on older GCCs. Bad error reporting. So I use it sparingly. I think I used to use it a lot more, but I've been replacing with Windows APIs on Windows and Linux APIs on Linux and stuff. But I don't have my own SED file system path abstraction. My code isn't dealing much with paths, so I could just use string. Probably should make a path abstraction though, but you know, there's a cost to abstractions like that. One is probably a lot of code. I mean, not a lot, but yeah. Do I use exceptions in Quicklin.js? No. I do have a custom exceptions thing called try catch stack, which uses set jump long jump. And you know, I have like a, it's a wrapper around set jump because it's a way to, it's not just a way to do set jump long jump. You also can transfer data like an exception object kind of thing. And you can also nest it and stuff. So I have that, but it's not general exceptions like C++ exceptions. The reason I do it this way is so that I can, so one issue with C++ exceptions is that it will basically inject code for every function. I mean, there's a bunch of different things that happen when you enable exceptions. Basically, this is better for my code base. Usually the people who want exceptions want to avoid things like memory leaks or lock leaks. I don't use this code when there's locks involved, so I won't forget to unlock something if there's an exception. Whereas like with standard exceptions, if you have a lock guard and you throw an exception while the lock is held, when the runtime raises the exception and bubbles up the stack, it'll say, oh, there's a lock guard here. Let me unlock it for you and then keep throwing. That's all automatic. I don't need that behavior. So I don't need the overhead involved with the runtime keeping track of all that stuff. So I wrote my own thing that skips around all that exception overhead. It also reduces binary size because the unwind tables do take up a lot of space. Does the try catch stack thing unwind stack or is it just jump and doesn't call destructors? It does not call destructors. It's actually undefined behavior. The try catch stack actually invokes undefined behavior. In practice, that means it may or may not call destructors. So on Linux, it does not call destructors. On Windows, MSVC, it does. And on Windows, GCC, it doesn't. So it really depends on the compiler and stuff. That's another reason I use GCC because it doesn't call destructors. It's undefined behavior, set jumping, or long jumping across frames that have destructors that's undefined behavior. But in practice, does what I want. Have you tried other error handling things like system error or stood expected? I've tried system error, it's garbage. I've tried a similar thing to stood expected and I use it in a few spots. Mostly the configuration stuff because we need to load config files and there's various errors that can happen. If you try to load, we can have, I made a result class which is kind of like stood expected except there's multiple error types. So here's what comes out. So you ask to watch and load, you get this out or you get an error, which is one of these two. So this is like a variant, I guess, stood variant. So it can give you a canonicalized path IO error because it couldn't canonicalize or it failed to read the file. Or you could just do one error type if it's just one error. I don't think I have those here. Oh, there's like, the canonicalize function can return a canonicalize error, read file function returns a read file error. But if you call both of these, you might get either error. So that's what these guys down here do. So it's like stood expected. I use different error handling strategies for different things, of course. Do you use any dynamic static analysis tools for your C++ code like LibAsan? I do test with Asan and Ubisan regularly in CI. I also test with multiple different compilers and when you test with multiple different compilers, you expose different quirks. Well, you expose quirks of the compilers but you also, you know, you have multiple angles of looking at your code and that catches issues. That's one benefit of having an open source mindset is if you are open to a bunch of different compilers and you can possibly catch more issues. Each compiler will have its own static analysis stuff in it like warnings, like the vexing parse I showed you. I use that, that's static analysis. I don't use ClangTidy or any other like more sophisticated stack analysis tools. I found that just too noisy and don't really help. I try it occasionally. I try ClangTidy once every year or so. So I have noticed issues where like, I mean, maybe I have code like this where I have a case and then some code and then another case and some code and the two blocks of code are identical. ClangTidy will point that out and usually it's just like, oh, the code is working. Like here, the code is working correctly. I could just use a fall through to simplify the code. But I mean, the ClangTidy check is telling you, hey, there might be a bug because you copy pasted code. So it would be helpful if it caught bugs, but I've not caught any bugs with ClangTidy, but I haven't used that much, so I don't know. But for like shortening code, that kind of stuff's helpful. CI does check with a bunch of different compilers. Sometimes it's not W error zone. Earlier when I changed compile flags and rebuild, you saw there was some warnings. That's in the library. I don't error on all warnings, just on warnings in my code because third party libraries will have warnings that I can easily fix or I don't wanna fix. Also on, I only do warnings as errors on GCC and Clang. I don't do on Microsoft's compiler. And that's because I tried setting it up and I thought it was working on CI, but it wasn't working, but then I kept introducing new warnings. And by the time I noticed that, oh, there's still all these warnings, my warnings is everything isn't working, I was like, eh, I don't care. But I should go back and fix the warnings everything on MSVC and fix all the warnings. I should do that. I just haven't gone around to it. So I don't normally develop with MSVC. If I did, I would notice some warnings and try to fix them. Okay, so I think we'll wrap up that session. I will probably do another one of these and discuss different parts of the code base or different things. Maybe I'll get more in depth. I didn't really talk about classes, for example. I didn't talk about templates. I didn't talk about my workflow, debugging cycle kind of things. There's plenty of things I wanted to cover that I didn't get to. I don't know, we'll see. Depends on what people ask and what I'm interested in talking about.