# Stroustrup's PPP3 Study The last time I did C++ seriously was 1996. That was 28 years ago. Damn I'm getting old. If I'm going to use C++ to make some games and teach people how to code C++ then I better relearn the latest stuff. To relearn C++ I bought [Stoustrup's Principles and Practice Using C++ 3rd Edition](https://www.stroustrup.com/PPP3.html), or PPP3 for short. This part of the repository are my study files so people can read them, and my critique of the book as I go through it. ## Getting the Code to Work It uses [meson](https://mesonbuild.com/) so if you got the `sfmldemo` to work then you should be ready to go. You need to do this: 1. `mkdir builddir` 2. `meson setup builddir` 3. `meson compile -C builddir` The "exercises" are then compiled into `builddir` so to run `ex06.cpp` you do `./builddir/ex06`. ## Editing the Code I'm using Vim with [clangd](https://clangd.llvm.org) and [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) for checking the code while I work. I also use Tim Pope's [vim-dispatch](https://github.com/tpope/vim-dispatch) to run the builds but there's a slight problem with how Meson is setup. Meson requires you to do the builds in `builddir`, and it does this by doing a `cd builddir` then running `ninja`. This keeps the source tree clean of build junk, but now all of the error messages from the C compiler are in the wrong location. They'll list an error in `ex06.cpp` as `../ex06.cpp`. Vim can pick this up, then then it goes one directory up to find the file. It's incredibly irritating that Vim doesn't have an ability to adapt to this given how many C++ build tools use this pattern, but the way I solve this is simple: 1. `:cd builddir` 2. Run the build in Vim and now the error paths will be correct. 3. Just remember to edit files with `:e ../ex06.cpp`. This is dumb as hell but it's working for now. If you have a fancy way to tell Vim "Hey, all paths in the error buffer are one level up" then let me know. ## Criticisms of PPP3 1. __Bad support code and future proofing.__ This book is attempting to teach C++23 (the 2023 version of the standard) in a world that doesn't correctly support 2023. To make this work he has these header files `PPP.h` and `PPP_support.h` and other random files you're supposed to include with no explanation on how to _actually_ use them. When I try use them (they're included in this repo) they don't compile. I could spend the time to make them work, but it's actually easier to just take his code and make it work without these headers. 2. __Nothing actually works as written.__ Not a single example given in the book actually compiles. Header files are skipped even when they'rr essential to the code's function. He just assumes you'll use `PPP.h` but that doesn't even include everything correctly, and as I said, his files don't even work without a mythical advanced compiler from the future. Also, relying on `PPP.h` means people don't learn the real includes needed to make the code work. 3. __No full sample code available.__ Normally in this situation I'd go find the sample code download or see if there's a github repository of the code, but all of his links to sample code on the [his website for the book](https://www.stroustrup.com/PPP3.html) are dead and sometimes labeled "TBD". 4. __Disjointed incomplete examples.__ There's all kinds of examples that feature code from one part of the system that relies on another part that either doesn't exist or isn't explained well. A good example is the calculator, where you're making a parser. In Chapter 5.6 he has code for parsing the calculator language that all use a `Token_stream` class, but only implements the `Token_stream` _after_ all the parsing code. This means that none of the parsing code he's talking about actually works until you get to the `Token_stream` later, which is backwards. Many examples are also progressively improved versions of functions but that require later code to even work. 5. __No explanation on actually building the code or setting up an environment.__ In the very beginning he claims the book is for people who have no programming experience but want to work hard. He then dives right into the classic "hello world" but no explanation on how to get that to compile. No instructions for installing software, no explanation of build tools, or compilers that work, nothing. You might think this is to be "high class" and not focus on such "trivial nonsense" but there's only like 4 compilers he has to worry about, 2 if you want only C++23 features. He could totally figure out instructions for 2 compilers. Without instructions on how to properly setup and compile that first version the book is completely useless for the total beginner he claims to target. Even as a seasoned professional I couldn't figure out how to get his `PPP.h` to work, so how is someone who knows nothing about C++ supposed to figure it out? Since the code samples are incomplete, how is a beginner supposed to know they need `#include ` to make `std::cout` to work? 5. __Disorganized presentation of the core concepts.__ The book is all over the place. First you cover objects, types, and values, but this doesn't cover objects it covers base types like `int`, then it goes into computation but nothing about classes and struct, then he gets into error reporting with a reporting method that doesn't work without the `PPP.h` file you can't use, but then gets into writing a calculator that uses `class` but no explanation of that until Chapters 7 and 8 which finally explains all the things you needed to work on the calculator. It's understandable to not cover things until you can explain them later, since programming is so complicated you do have to gloss over concepts until later. But, Chapters 7 and 8 should have probably replaced the majority of the content in chapters 1-4 before he gets into making code with the concepts. That's my critique for far. Basically, the book is your classic code book where the author has absolutely no idea who he's writing the book for, and just assumes that everyone who reads it has his brain and his computer. He most likely has a whole directory full of the code in the book but you and I will never see it. It's also _definitely_, _DEFINITELY_ not a beginner book. I couldn't imagine someone who's never written code trying to setup MinGW with Meson on a Windows machine or even getting Visual C++ to work, let alone figure out his mishmash of `PPP_*.h` files. 6. __Constantly using things that don't exist.__ The book is riddled with things that are claimed to be supported or standard, but that isn't available in many compilers. A great example is `std::format`, which would be great since I planned on having people use the excellent `fmt` library, but none of the compilers I have had this. He does this all the time where he just assumes something exists but it doesn't, and what's even more irritating about this is it's not hard at all to test. There's only like 4 compilers to verify. A single test suite could do it, but nope, I'm forced to research it myself. ## The Weirdest Bug of 2024 I'm going to explain a super bizarre bug in the hopes that someone else runs into it and can explain to me what is going on. This is a compilation error on Windows that made no sense from the start, and then _magically went away one day with no changes from me._ If you get errors in `libgit2/src/utils/process.h` then see if it matches my description here. I had the `watchgit.cpp` file working on OSX and decided to test the build on Windows. The only thing that failed on the build was `libgit2/src/utils/process.h` had several errors regarding missing types. To keep this description short here's the patch I crafted that solved the problem: ```diff --- subprojects/libgit2-1.8.0/src/util/process.h 2024-03-20 16:19:37.000000000 -0400 +++ process.h 2024-05-09 08:05:48.279986200 -0400 @@ -8,6 +8,8 @@ #ifndef INCLUDE_process_h__ #define INCLUDE_process_h__ +typedef struct git_str git_str; + typedef struct git_process git_process; typedef struct { @@ -112,10 +114,7 @@ * cmdline arguments to ensure that they are not erroneously treated * as an option. For example, arguments to `ssh`. */ -GIT_INLINE(bool) git_process__is_cmdline_option(const char *str) -{ - return (str && str[0] == '-'); -} +#define git_process__is_cmdline_option(str) ((str) && (str[0]) == '-') /** * Start the process. ``` What's even stranger is that the compilation errors _only_ showed up when I included any C++ headers. For example, here's code that I used to turn off/on headers to see if one was the culprit: ```c // #include // #include // #include // #include // #include // #include #include "libgit2-test.h" int main(int argc, char *argv[]) { test_libgit2(argv[1]); } ``` What's wild is do you see how I have a `test_libgit2` function and I'm including a `libgit2-test.h` file? I even went so far as to place all of the `libgit2` code into a separate compilation unit to firewall it off and the bug _STILL_ showed up. That `libgit2-test.h` header had absolutely zero `libgit2` headers or definitions. It only had the one `test_libgit2` function in it. Yet, even after firewalling the code off in its own `.o` file I would still get the compiler error if I uncomment any of the above C++ headers. To summarize, here's the compilation bug so far: 1. I receive errors in only `src/utils/process.h` in the libgit2 project. 2. The errors are various complaints about `struct git_str` missing and `GIT_INLINE(bool)` macro not creating a proper type so the compiler complains that `git_process__is_cmdline_option` has an invalid type, and the usual string of bad syntax errors when syntax is missing. 3. These errors show up any time a C++ header file like `` or `` is included. 4. These errors _even_ happen when you firewall the `libgit2` code into a totally separate compilation unit, which should be impossible. 5. These errors _EVEN_ happen in MinGW 13 (released 2023) and clang 17, the latest ones I could get. The fact that the error happens in two wildly different compilers tells me it's a bug in the `libgit2` code. 6. Adding the `typedef struct git_str git_str` forward declaration and converting `git_process__is_cmdline_option` to a macro fixed the errors and allowed it to compile. Thinking I'd mostly figured it out, and having a valid fix for the compiler error I start to work on a patch to submit to the `libgit2` project. I spent an _entire_ day just trying to figure out how `meson` wants it's patches before giving up. Life is too short. After giving up, I was about to write a hack script that just copied the `process.h` over and then...it started working. At first I didn't believe it and though maybe I'd applied the patch successfully. I delete the patch and reverted the changes and...it compiled. One day later and suddenly it's as if the build is working and I did not do anything to fix it. I suspected that maybe I had a strange environment or something about my machine. If it's suddenly working then I can't say it's a `libgit2` problem. I did need to figure out what was causing it though and tried the following: 1. For some idiotic reason meson loves to install random junk into Anaconda's directories. I think it looks for the first bin/lib/include directories in the PATH and uses those, and Anaconda's is first, so that's about the dumbest shit I've ever heard of doing if that's true. 2. That made me think that maybe enabling an anaconda environment would cause it to fail but nope, works fine still. No weird errors when using C++. 3. I thought it might be ccache related. Maybe ccache had a corrupted cache and it "magically" got cleared out this morning? If that's the cause then there's no way to replicate it other than keep coding and hope it comes back. 4. Another thing was all of the errors pointed at the includes in the default MinGW install, even if I used clang, which is again so dumb. Why would clang pick up a random header file in the mingw install? But, again everything is fine now. 5. I completely removed Anaconda and all previous files I'd installed from testing the build, rebooted, triple checking I had a completely clean setup, and it _still was working_. 6. A final thing I suspected is _maybe_ ccache was skipping recompiling? As in, somehow I managed to trick ccache into using the one working copy it made and didn't update? Just in case I ran `ccache -C` to completely clear it and it still compiles with no errors. At this point I can't reproduce the compiler error I'd had for an entire week and this worries me. I'm positive other people will randomly run into this, so rather than continue trying to make it happen I'm going to continue development and wait for it to show up again. If you read this and think you might know what is causing it, feel free to checkout the code and try to reproduce it. The failing build is in the `PPP3` directory.