Depends. If you write C++ like it was C then yes, it's more readable. But if you use all the modern stuff to make it behave closer to Rust it becomes much uglier.
[[likely]] does not help branch prediction, in fact there's nothing you can do to force branch prediction. [[likely]] optimizes branches, inlines where possible to make code run faster. Most of the compilers have an internal % system which tries to predict which branch is more likely your code to take, and based on that % it optimizes it by inlining, [[likely]] is a way for the programmer to raise that % for a certain branch. Nothing to do with branch prediction - common misconception. To be able to use [[likely]] without pessimizing instead of optimizing you need deep understanding of how your complier optimizes the code in the first place, to know where it uses lookup tables. Usually you shouldn't use that C++ feature unless you've studied the assembly your compiler generates and the performance you're getting out of it and are willing to spend the time on even the last nanosecond.
+1 This is to be used only when there is heavy operation on one branch, but is not really likely to happen. Like, say, you want to push some big strings into a set, otherwise return, and you know that the existence of a string in the set is more likely. This is where this attribute might help, and lead to lesser code generation. This will not, as correctly mentioned, predict branches. The compiler might still fully ignore it. To include this attribute in such "lightweight" branches is really unnecessary.
@@TheVimeagen it's really just standardizing a long long history of implementation defined attributes. You don't need to care about it pretty much ever, it's a bit extra here.
The [[likely]] is a hint to the compiler to let it make assumptions it usually can't. It can then make the likely path branch-free, or emit hint instructions for the cpu (or similar). This can be useful in certain scenarios if the cpu branch prediction fails systematically (probably not here). Note that rust has something similar with the #[cold] attribute
oh, CheaterCodes. When you say "similar" does that include having different speeds? when it gets to the instructions, do they differ in any meaningful way? Does LLVM vs GCC have an effect on this (for C++ as Rust is only LLVM AFAIK)?
Lmao, this [[likely]] reminded me of this: "INTERCAL has many other features designed to make it even more aesthetically unpleasing to the programmer: it uses statements such as "READ OUT", "IGNORE", "FORGET", and modifiers such as "PLEASE". This last keyword provides two reasons for the program's rejection by the compiler: if "PLEASE" does not appear often enough, the program is considered insufficiently polite, and the error message says this; if it appears too often, the program could be rejected as excessively polite. Although this feature existed in the original INTERCAL compiler, it was undocumented"
[[likely]] has nothing to do with the brand prediction. It's a locality optimization that means the [[likely]] branch will be close to the code preceding and succeeding the branch, with the [[unlikely]] branch further away. The only time there needs to be a potentially far jump that might cause cache misses would then only be in the [[unlikely]] path. It's for e.g critical path optimization where you might want a path that is not necessarily "hot" in that it might not run frequently, but it should run *as fast as possible* regardless. An example might be a trading system where most of the time the potential trades are rejected by a branch, but if in the minority of trades the trade is not automatically rejected you want the execution to be the fast branch. You can manipulate the branch predictor by spoofing/warming the branch but you can't cache optimize branches without the [[likely]]/[[unlikely]] language feature. It's very cool 😎
7:20 AFAIK the way the compiler usually uses the "likely" and "unlikely" builtins is that they will place the "unlikely" branch code away from the main program flow, so the main execution path fits in less cache lines.
[[likely]] So its maybe easier to understand if you flip it on its head and look at [[unlikely]] instead. In that case you are telling the compiler that this code path is super unusual and will let it make better inlining / conditional choices. For example an assert() implementation by definition is unlikely to fire and the assert handling code (for when it fires) could benefit from being moved out of line thus improving the general performance as less instructions and branching can be run in the non-asserting case.
for a [[likely]] example, think of std::vector push_back. In most cases it won't need to reallocate memory (especially if you are a good citizen and remember to reserve up-front), so could potentially benefit from the hint when it checks if it has enough capacity before the physical object copy. In this case it could potentially move the else and resize for when that fails out of line (by definition they would now be 'unlikely'), and thus get better performance (less instructions run in the common case) and benefit from better comdat folding (smaller code due to de-duplication - a common linker trick where it removes duplicated code which can also helps icache (instruction cache) efficiency --- less code == generally more potential cache hits). Think how big code could get if every 'push_back' has the explicit resize code duplicated in it,
A few things to note here, as a recovering C++ user: * For some reason, compilers start implementing standards way ahead of the year. They're adding C++27 features right now 😮 * I think private first, trailing _ for members is Google's house style? It's overall pretty nice, but the point of the underscore is so you can directly reference them without the "this->" unambiguously, so it's lame those are still there... * Standard library source is run through uglifiers so every identifier uses the reserved "_Uppercase", and every reference is fully qualified, to defend against macros breaking things. I don't think they actually edit the source like that! * noexcept is a replacement for except(), which is now completely deprecated. The difference, IIRC is that it's part of the signature so you can specialize a template if you know the generic function definitely won't throw, eg avoid putting expensive rollback handling. If/when Rust gets specialization this would be like specializing on error type !. Here it's not doing much other than checking the body can't throw, eg that every method it calls is also noexcept.
additional fun fact - move operators have to be noexcept, too (but I guess it depends on compiler flags, as exception during move can brake source object without, effectively losing data) - which, knowing cpp, can be just "we don't give a fuck; UB, but gib sped"
@@widnyj5561 as far as I am aware move operators definitely don't have to be noexcept. However move operations for obvious reasons, like the one you stated, should be implemented in ways that it doesn't throw. And seeing as how move operations technically don't do allocations (necessarily, they can, but for brevity's sake, lets say they don't) one can be pretty certain it's a non throwing operation. What I find unfortunate is that all my private code for hobby projects gets absolutely littered with noexcept everywhere. It should be the default.
@@simonfarre4907 you actually don't want it as a default in general, for the same reason you don't want Copy by default in Rust: it's a breaking change to remove it, while it's fine to add. If you later find out that you do want to throw (or rather, use other APIs that can throw) then you're stuffed.
@@simonfarre4907 no, I mean backwards compatibility problems for your changes, not if they added it now. Imagine your public function happens to not throw exceptions, you left it at the default, then you change it to throw later. Obviously just throwing when you didn't throw before is a compat hazard, but in C++ the default assumption is anything can throw, so currently this is ok. But with nothrow, you can actually explicitly depend on the function being nothrow, most obviously by calling it from another nothrow function. Just removing the nothrow qualifier is a hard breaking change! For this reason, you should only add the qualifier when you're making it part of the API guarantee, not just because you happen to but currently throw anything. Making it the default is then a problem for two reasons: you're much more likely to accidentally expose a public API as being nothrow, and you're much more likely to accidentally depend on it being nothrow by calling from a nothrow function.
I captured an image from the code here to create the dirty code poster, unreadable, hard to maintain, a nightmare to inherit. The ego of expressing your logic in terms of your implementation details rather than using the requirements language, robing the program of the opportunity to document the intension of the product in the code itself (while it was clear and fresh in the author's mind), assuming there will be no bugs, no changes in the requirements hence no need to open the blackbox and no changes in team roster, I love it.
Ive never moved passed C++17 and I saw some very new things. I liked the likely though. But let’s be honest, it doesn’t make the code more readable. But it does optimize away a likely branch something we did in demo coding in assembly on the C64 all the time.
5:27 Yes, function return type on its own line is useful for grepping function names without clever tooling; but also this is C++, where you often have like a function template returning a templated type so splitting all that to even more lines is just a reasonable thing you do and doing it for simple types as well is then just for consistency.
Rust has something similar to '[[likely]]', in core::intrinsics::{,un}likely. Way it works is it takes a bool and returns it, and adjusts the likely/unlikely path to be more/less likely to be branch predicted and CPU cached, I believe. You can use it like `use core::intrinsics::likely; for i in 0..15 { if likely(i % 5 != 5) {foo()} else {bar()} }` (obviously a super contrived example but you get the point) Where do I find the full spec for this thing? I might want to have a dab at it with Z Shell or perhaps elisp. My personal specialty is in writing long shell pipeline abominations in ZSH. If I could horrify others with it that'd be nice
I would avoid using [[likely]] unless you are very sure of your assumptions. Do profile-guided optimization (PGO) not assumption guided optimization . Either-way this could help a lot with automatic unrolling of loops properly. Of course, gathering and keeping real world data up to date is difficult as Itanium proved.
Well sometimes you know for eg: a certain condition will only hold true for the first k items in the loop. Let's say you don't want to loop over those items in a separate for-loop. I find this could be useful in those cases.
I just realized people might not be familiar with PGO, or profile guided optimization. You compile a version of the program that produces profile data. On gcc you use '-fprofile-generate' then you run it with inputs or data representative of typical usage. (Or you could deploy it. ) The program will create '.gcda' files. Then you recompile it with -fprofile-use and either '-O2 ' or '-O3' and whatever else you need. The profile will help the Compiler figure out how to best do the optimization. Remember, the itanium was nearly completely reliant on profiled optimization, since it's was designed to avoid doing Branch Prediction and Speculative Execution. (Which I believe was the word Primeagen was looking for)
PGO has some disadvantages, e.g. if you make bad train data, compiler can easily "optimize" your program and it can be slower even x10. If you make some changes in your code, you need to get profile data again. In contrast, hints will live and be reliable until some big refactoring
0:30, that's because it's indeed the best so far. aka Rust+. 2:00, private is default for class, as public for struct. This private line was optional. 2:10, it's a convention, for when you want to write getter/setter with the same name. Since those f() calls will be much more often seen than the class inner data, these ones are those who change their names, by adding '_'. Not before, due to macro convention. 2:20, this convention was created by a hungarian programmer, who used to add a letter for the type of the variable. 2:47, it's entirely optional. I use to write struct, to get public as default (1st, as you say). Because if I want to use a typedef along the class definition, I need to declare it before. And since I want it to be used outside the class too, it needs to be public, to not has to break encapsulation - btw, C++ has the best from any language. 3:08, an union with tags. It's meant for when you want more than 1 variable beginning at the same address in memory. This saves memory, at the cost of only 1 may be used at a time. 3:29, it has all those scopes (::) because C++ has a philosophy of "only pay for what you use". It makes easier to write more light weight apps. So everything is subdivided into optional "sections". static_assert is for defensive code proposes, to raise compile errors if the user does something wrong.
I really tried to make sense out of those Template and Type creations, but even with all the JDSL jeniusness I could come up with a simple explanation of the code.
At 6:26 why are there explicit this-> strewn all over the code? There's already the underscores appended to mark member variables. Even without that is highly irregular. Unlike the private first.
this-> is unnecessary here, but it does make sure the name is still accessible even if defined in a base class so maybe they were future-proofing for that? Idk I wouldn't write it that way either.
It is to differentiate members from non-members, not public and private. Not all members will have the private underscore suffix if there are any public. It may not strictly be necessary, but some of us prefer the explicitness.
Using this-> instead of having your parameter names different from the member names makes everything ugly. Headers are usually the only place where 'using namespace' is dreadful. Using std:: is preferred in headers, but part of the reason C++ often looks so ugly is the lack of adding 'using namespace' in .cpp files. In 28 years of programming I've never found it causing problems. You would never use the full names in C#, if you did the same as alot of C++ code does in C#, it too would look pretty ugly.
There is a reason for that. If you eventually add a return type to the function, you'll get a compiler error. Without the empty return, you're only going to get a warning on default settings.
That constructor 1 introduces implicit conversion from std::string 2 copies the string twice 3 const does nothing except confusing the reader 4 throw std::exception() is punishable by death 5 return does nothing at all . Noexcept makes it call terminate if exception is thrown, no stack trace. Optimising branch prediction in the code that is 10% complete is a bad sign. Requiring cpp23 and not using generator for lexer is strange, as is using both discriminator and std::variant in token. Also, std::string for multi byte token means heap allocation, use string_view or indexes for both single- and multibyte tokens, that will allow you to report line/column in error messages.-Wall, clang-format and asan are your friends.
[[likely]] is just a weaker and standardized version of what has existed for a long time in C/C++ e.g. in the form of __builtin_expect(). This is IIRC what also Linux kernel also uses (the likely/unlikely macros typically expand to __builtin_expect() if supported by the compiler).
The sad thing about that snippet was that ` private` at the beginning of a ` class` is redundant. You can just slap members in there, and everything before the first ` public` is implicit ` private` .
@@NoName-y2e6c that's reasonable, I can see the upside in that. Also it makes it possible to change between class and struct with no reordering of members. Although that sort of thing isn't very common.
4:09, it's just a more protected collection of integers. Not as good as Rust enums. But I guess it's possible to emulate that with an array of variants... 4:40, you can store anything in a std::map (it's a template class afterall). However, it's infamous, because it seems that it's implemented as a linked list, which is a fairly slow data struct. So often someone is seen implementing his own version of it (which I recommend, btw). 4:51, did you just swallow a bank card? 5:18, this prevents an annoying potential error from fast C initializations. 5:24, it's really bad. I don't use, neither recommend. 5:37, they are deactivated for f()s marked as noexcept. 6:57, it's a hint for the compiler, to optimize the code: it's likely to happen this than the other alternatives. And those 'this->' are unnecessary: by default the class variables are used; there's no ambiguity. 7:40, with old style enums, I like to reduce this kind of code to 3-5 lines, by creating sorted arrays, searching the char in 1, via some algorithm, and using the resulting index on the other. It's as fast as elegant. 8:20, this is just a reference for a class object that lives on the stack memory, meaning it's not allocated by the user. This token may be string or a char at a time. std::string makes automatic allocations, so it'll free its memory for you. The reference works as the borrow from Rust, I guess. What changes from it in this f() will remain after.
std::maps are not implemented as a linked list, they're a red-black tree (I suppose AVL might work as well, but red-black is the most common). exceptions are fantastic and very much recommended. In some cases mandatory (such as here).
[[likely]] and [[unlikely]] just order the code so that the "most" likely branch is the closest to the conditional jump. But it is not as good as it seems for modern CPUs (like < 15 years old lol)
I always do the member variables that are private up top, first. Because these, _always_ turn out to be the most interesting and crucial things. Think of it as attempting to look like Rust, without actually being good, like Rust.
@@vladimirkraus1438 You can think of it like this; a public API is only ever important in library development. It needs to be stable, it needs to produce the same outputs, no matter it's internal details (which can change under the end-user's feet from version to version, but still produce the same output). This is where public API is of import. For application development, like say, a debugger like GDB or RR; why would the public API be the most important? They're not libraries. What's most important is their functionality, their performance. Any change to the underlying system _will_ be changes to the implementation details. This means, what data structures are being used, what is the layout, when are they read and written, so on and so forth. These are all implementation details. Rust was *brilliant* when they realized this and separated the declaration & definition of a type and it's implementation, or behavior.
@@simonfarre4907 Why do you distinguish writing library or application? All is just a code. And if implementation details are the most important to you, then you are doing something wrong. Btw. how are you ordering the methods? Private ones first at the top too? Then protected ones and public ones at the end?
@@vladimirkraus1438 False. Application development and library development differ *massively* in what requirements and constraints are placed on both the developer and end product. The ordering of methods is irrelevant, but after the definition of the actual data type, I proceed like usual, public first. There's plenty of applications that you use *right now* that do what I've said here.
That lexer::read_char() method is really bad: A void argument, pointless usages of this, using += 1 instead of ++, a pointless return, etc. It's really weird. EDIT: It looks like the constructor also makes an unnecessary copy of the input string.
would be cool if [[ likely ]] was used to tell the compiler which branch is "more likely" (duh), so that it performs sort of a hotspot optimization like JITs do, analyzing the runtime. Just that in this case there is no analysis of course, but it assumes the programmer's directive is reflective of runtime behavior. I am really afraid JITs will get significantly better than AOT compilers, and that I'll be forced to migrate to Java 🤮.
Wow, Is like seen two old guys just talking about C99 :D Come on!!! You streamer guys should try to make a tetris in C++23, and will see THE POWER of: Spending a week to make a Cmake, Spend another week to try to add Conan/vcpkg, try to install SDL2/raylib. Then after a month, you will get beard, so you can now, Spend another week why cmake does not recognize SDL2/Raylib. And now, after two months, you are ready to start coding C++23. Thats how to do it :D
The only reason why most programmers choose to define private member before public is to access private member variable in public function. Otherwise cpp compiler starts to puke 🤮 right back to you. It’s like I don’t recognise it, who is he/she ahhhhhhhhhhhhhhh😂
Variant on a string, char is dumb. String has an internal variant on its pointer for use with the small string optimization at least on most standard library implementations. So doing that yourself is likely useless
The fact is that CPP is the C pre-processor, which is the true pinnacle of elegance. C++ tried to beat the pre-processor's advanced feature set, failed miserably, and stole the name out of spite.
@@jarvenpaajani8105 I'm not a developer, but the code does look peculiar. 'return's at the end of functions returning void; underscored members with "this" everywhere, "next_token" could be much shorter, say, try {t.type = type_from_char.at(byte_); t.literal = byte_; } catch (const std::out_of_range& unknown) {.. deal with it.. } read_char(); where type_from_char is a respective map. Some stuff, such as 'final's, [[likely]], std::variant, I don't understand why it's needed, but, again, not a developer, maybe it improves performance.
The biggest problem is a lack of familiarity with the type system. Using a string is making copies of things everywhere. Using string view means the buffer exists once and then everyone has references into that same buffer. Many cases where this program would benefit from type parameters.
it's funny to see how json engineers trying to make jokes out of c++
lol
I don't feel that a cpp project is ready when there are no memory leaks to be found. I hope they fix it on v1.1
imo cpp code is more readable than rust
it’s true
fully agree
c++98 maybe, not the latest c++20/23 stuff
3:28 is that readable?!
Depends. If you write C++ like it was C then yes, it's more readable.
But if you use all the modern stuff to make it behave closer to Rust it becomes much uglier.
[[likely]] does not help branch prediction, in fact there's nothing you can do to force branch prediction. [[likely]] optimizes branches, inlines where possible to make code run faster. Most of the compilers have an internal % system which tries to predict which branch is more likely your code to take, and based on that % it optimizes it by inlining, [[likely]] is a way for the programmer to raise that % for a certain branch. Nothing to do with branch prediction - common misconception.
To be able to use [[likely]] without pessimizing instead of optimizing you need deep understanding of how your complier optimizes the code in the first place, to know where it uses lookup tables. Usually you shouldn't use that C++ feature unless you've studied the assembly your compiler generates and the performance you're getting out of it and are willing to spend the time on even the last nanosecond.
+1 This is to be used only when there is heavy operation on one branch, but is not really likely to happen. Like, say, you want to push some big strings into a set, otherwise return, and you know that the existence of a string in the set is more likely. This is where this attribute might help, and lead to lesser code generation. This will not, as correctly mentioned, predict branches. The compiler might still fully ignore it. To include this attribute in such "lightweight" branches is really unnecessary.
weird.... yeah, i have not much idea about some of c++'s things
@@TheVimeagen it's really just standardizing a long long history of implementation defined attributes. You don't need to care about it pretty much ever, it's a bit extra here.
That's not entirely true in the general case. There are architectures where a hint can be generated for the processor(for example the spus on PS3)
@@TsvetanDimitrov1976 Благодаря за уточнението! :)
The [[likely]] is a hint to the compiler to let it make assumptions it usually can't. It can then make the likely path branch-free, or emit hint instructions for the cpu (or similar). This can be useful in certain scenarios if the cpu branch prediction fails systematically (probably not here).
Note that rust has something similar with the #[cold] attribute
oh, CheaterCodes.
When you say "similar" does that include having different speeds? when it gets to the instructions, do they differ in any meaningful way? Does LLVM vs GCC have an effect on this (for C++ as Rust is only LLVM AFAIK)?
Lmao, this [[likely]] reminded me of this:
"INTERCAL has many other features designed to make it even more aesthetically unpleasing to the programmer: it uses statements such as "READ OUT", "IGNORE", "FORGET", and modifiers such as "PLEASE". This last keyword provides two reasons for the program's rejection by the compiler: if "PLEASE" does not appear often enough, the program is considered insufficiently polite, and the error message says this; if it appears too often, the program could be rejected as excessively polite. Although this feature existed in the original INTERCAL compiler, it was undocumented"
[[likely]] has nothing to do with the brand prediction. It's a locality optimization that means the [[likely]] branch will be close to the code preceding and succeeding the branch, with the [[unlikely]] branch further away. The only time there needs to be a potentially far jump that might cause cache misses would then only be in the [[unlikely]] path. It's for e.g critical path optimization where you might want a path that is not necessarily "hot" in that it might not run frequently, but it should run *as fast as possible* regardless. An example might be a trading system where most of the time the potential trades are rejected by a branch, but if in the minority of trades the trade is not automatically rejected you want the execution to be the fast branch.
You can manipulate the branch predictor by spoofing/warming the branch but you can't cache optimize branches without the [[likely]]/[[unlikely]] language feature. It's very cool 😎
7:20 AFAIK the way the compiler usually uses the "likely" and "unlikely" builtins is that they will place the "unlikely" branch code away from the main program flow, so the main execution path fits in less cache lines.
Tom might [[likely]] be the editor for CppCoreGuidelines 😆
so this is sort of non standard formatting for C++
[[likely]] So its maybe easier to understand if you flip it on its head and look at [[unlikely]] instead. In that case you are telling the compiler that this code path is super unusual and will let it make better inlining / conditional choices. For example an assert() implementation by definition is unlikely to fire and the assert handling code (for when it fires) could benefit from being moved out of line thus improving the general performance as less instructions and branching can be run in the non-asserting case.
for a [[likely]] example, think of std::vector push_back. In most cases it won't need to reallocate memory (especially if you are a good citizen and remember to reserve up-front), so could potentially benefit from the hint when it checks if it has enough capacity before the physical object copy. In this case it could potentially move the else and resize for when that fails out of line (by definition they would now be 'unlikely'), and thus get better performance (less instructions run in the common case) and benefit from better comdat folding (smaller code due to de-duplication - a common linker trick where it removes duplicated code which can also helps icache (instruction cache) efficiency --- less code == generally more potential cache hits). Think how big code could get if every 'push_back' has the explicit resize code duplicated in it,
A few things to note here, as a recovering C++ user:
* For some reason, compilers start implementing standards way ahead of the year. They're adding C++27 features right now 😮
* I think private first, trailing _ for members is Google's house style? It's overall pretty nice, but the point of the underscore is so you can directly reference them without the "this->" unambiguously, so it's lame those are still there...
* Standard library source is run through uglifiers so every identifier uses the reserved "_Uppercase", and every reference is fully qualified, to defend against macros breaking things. I don't think they actually edit the source like that!
* noexcept is a replacement for except(), which is now completely deprecated. The difference, IIRC is that it's part of the signature so you can specialize a template if you know the generic function definitely won't throw, eg avoid putting expensive rollback handling. If/when Rust gets specialization this would be like specializing on error type !. Here it's not doing much other than checking the body can't throw, eg that every method it calls is also noexcept.
additional fun fact - move operators have to be noexcept, too (but I guess it depends on compiler flags, as exception during move can brake source object without, effectively losing data) - which, knowing cpp, can be just "we don't give a fuck; UB, but gib sped"
@@widnyj5561 as far as I am aware move operators definitely don't have to be noexcept.
However move operations for obvious reasons, like the one you stated, should be implemented in ways that it doesn't throw. And seeing as how move operations technically don't do allocations (necessarily, they can, but for brevity's sake, lets say they don't) one can be pretty certain it's a non throwing operation.
What I find unfortunate is that all my private code for hobby projects gets absolutely littered with noexcept everywhere. It should be the default.
@@simonfarre4907 you actually don't want it as a default in general, for the same reason you don't want Copy by default in Rust: it's a breaking change to remove it, while it's fine to add. If you later find out that you do want to throw (or rather, use other APIs that can throw) then you're stuffed.
@@SimonBuchanNz yeah I totally get why it's not default now, obviously. But with the privilege of hindsight, it should have been.
@@simonfarre4907 no, I mean backwards compatibility problems for your changes, not if they added it now. Imagine your public function happens to not throw exceptions, you left it at the default, then you change it to throw later. Obviously just throwing when you didn't throw before is a compat hazard, but in C++ the default assumption is anything can throw, so currently this is ok. But with nothrow, you can actually explicitly depend on the function being nothrow, most obviously by calling it from another nothrow function. Just removing the nothrow qualifier is a hard breaking change!
For this reason, you should only add the qualifier when you're making it part of the API guarantee, not just because you happen to but currently throw anything.
Making it the default is then a problem for two reasons: you're much more likely to accidentally expose a public API as being nothrow, and you're much more likely to accidentally depend on it being nothrow by calling from a nothrow function.
I was taught the "private first, public afterwards" approach, so that part of the code seemed normal to me
I captured an image from the code here to create the dirty code poster, unreadable, hard to maintain, a nightmare to inherit.
The ego of expressing your logic in terms of your implementation details rather than using the requirements language, robing the program of the opportunity to document the intension of the product in the code itself (while it was clear and fresh in the author's mind), assuming there will be no bugs, no changes in the requirements hence no need to open the blackbox and no changes in team roster, I love it.
Ive never moved passed C++17 and I saw some very new things. I liked the likely though. But let’s be honest, it doesn’t make the code more readable. But it does optimize away a likely branch something we did in demo coding in assembly on the C64 all the time.
5:27 Yes, function return type on its own line is useful for grepping function names without clever tooling; but also this is C++, where you often have like a function template returning a templated type so splitting all that to even more lines is just a reasonable thing you do and doing it for simple types as well is then just for consistency.
But he's using c++ 23, he can use trailing return types. It makes no sense to do that anymore.
Rust has something similar to '[[likely]]', in core::intrinsics::{,un}likely. Way it works is it takes a bool and returns it, and adjusts the likely/unlikely path to be more/less likely to be branch predicted and CPU cached, I believe. You can use it like `use core::intrinsics::likely; for i in 0..15 { if likely(i % 5 != 5) {foo()} else {bar()} }` (obviously a super contrived example but you get the point)
Where do I find the full spec for this thing? I might want to have a dab at it with Z Shell or perhaps elisp. My personal specialty is in writing long shell pipeline abominations in ZSH. If I could horrify others with it that'd be nice
because c++ types get stupidly long, you end up doing type function_name because std::map covers the whole line. (or auto)
and [[likely]] is a compiler intrinsic that allows it to optimize assuming the branch is true more often than false.
I would avoid using [[likely]] unless you are very sure of your assumptions. Do profile-guided optimization (PGO) not assumption guided optimization . Either-way this could help a lot with automatic unrolling of loops properly. Of course, gathering and keeping real world data up to date is difficult as Itanium proved.
yeah, i don't like the idea that i have to know what is the best course of action, i would rather have a compiler tell me :)
Well sometimes you know for eg: a certain condition will only hold true for the first k items in the loop. Let's say you don't want to loop over those items in a separate for-loop. I find this could be useful in those cases.
I just realized people might not be familiar with PGO, or profile guided optimization. You compile a version of the program that produces profile data. On gcc you use '-fprofile-generate' then you run it with inputs or data representative of typical usage. (Or you could deploy it. ) The program will create '.gcda' files. Then you recompile it with -fprofile-use and either '-O2 ' or '-O3' and whatever else you need. The profile will help the Compiler figure out how to best do the optimization. Remember, the itanium was nearly completely reliant on profiled optimization, since it's was designed to avoid doing Branch Prediction and Speculative Execution. (Which I believe was the word Primeagen was looking for)
PGO has some disadvantages, e.g. if you make bad train data, compiler can easily "optimize" your program and it can be slower even x10. If you make some changes in your code, you need to get profile data again. In contrast, hints will live and be reliable until some big refactoring
0:30, that's because it's indeed the best so far. aka Rust+.
2:00, private is default for class, as public for struct. This private line was optional.
2:10, it's a convention, for when you want to write getter/setter with the same name. Since those f() calls will be much more often seen than the class inner data, these ones are those who change their names, by adding '_'. Not before, due to macro convention.
2:20, this convention was created by a hungarian programmer, who used to add a letter for the type of the variable.
2:47, it's entirely optional. I use to write struct, to get public as default (1st, as you say). Because if I want to use a typedef along the class definition, I need to declare it before. And since I want it to be used outside the class too, it needs to be public, to not has to break encapsulation - btw, C++ has the best from any language.
3:08, an union with tags. It's meant for when you want more than 1 variable beginning at the same address in memory. This saves memory, at the cost of only 1 may be used at a time.
3:29, it has all those scopes (::) because C++ has a philosophy of "only pay for what you use". It makes easier to write more light weight apps. So everything is subdivided into optional "sections". static_assert is for defensive code proposes, to raise compile errors if the user does something wrong.
variant is template meta programming in compile time type checking added in cpp 17.
I really tried to make sense out of those Template and Type creations, but even with all the JDSL jeniusness I could come up with a simple explanation of the code.
Am I the only one who didn't understand a single thing that happened in this video?
They're like speaking Japanese
At 6:26 why are there explicit this-> strewn all over the code? There's already the underscores appended to mark member variables. Even without that is highly irregular. Unlike the private first.
because the -> operator is the most hackerman operator there is
this-> is unnecessary here, but it does make sure the name is still accessible even if defined in a base class so maybe they were future-proofing for that? Idk I wouldn't write it that way either.
It is to differentiate members from non-members, not public and private. Not all members will have the private underscore suffix if there are any public. It may not strictly be necessary, but some of us prefer the explicitness.
I hate the use of this-> which is mostly unnecessary, as you say templates is the one place where it is sometimes needed.
Using this-> instead of having your parameter names different from the member names makes everything ugly.
Headers are usually the only place where 'using namespace' is dreadful. Using std:: is preferred in headers, but part of the reason C++ often looks so ugly is the lack of adding 'using namespace' in .cpp files. In 28 years of programming I've never found it causing problems.
You would never use the full names in C#, if you did the same as alot of C++ code does in C#, it too would look pretty ugly.
Who wrote this? Returning from void at the end of a function? Do they know C++?
There is a reason for that. If you eventually add a return type to the function, you'll get a compiler error. Without the empty return, you're only going to get a warning on default settings.
That constructor 1 introduces implicit conversion from std::string 2 copies the string twice 3 const does nothing except confusing the reader 4 throw std::exception() is punishable by death 5 return does nothing at all . Noexcept makes it call terminate if exception is thrown, no stack trace. Optimising branch prediction in the code that is 10% complete is a bad sign. Requiring cpp23 and not using generator for lexer is strange, as is using both discriminator and std::variant in token. Also, std::string for multi byte token means heap allocation, use string_view or indexes for both single- and multibyte tokens, that will allow you to report line/column in error messages.-Wall, clang-format and asan are your friends.
[[likely]] is just a weaker and standardized version of what has existed for a long time in C/C++ e.g. in the form of __builtin_expect(). This is IIRC what also Linux kernel also uses (the likely/unlikely macros typically expand to __builtin_expect() if supported by the compiler).
im surprised both of you haven't seen private-first declarations in cpp; i had thought it was a common practice
The sad thing about that snippet was that ` private` at the beginning of a ` class` is redundant. You can just slap members in there, and everything before the first ` public` is implicit ` private` .
@@JuusoAlasuutari sure, classes are private by default but I like to be explicit about intentions
@@NoName-y2e6c that's reasonable, I can see the upside in that. Also it makes it possible to change between class and struct with no reordering of members. Although that sort of thing isn't very common.
@@JuusoAlasuutarido you mind explaining briefly to me when to use a class and when to use a struct?
@@calengo454 Use a class when it has invariants to maintain. Use struct when it's a bag of members with no nontrivial behaviors.
4:09, it's just a more protected collection of integers. Not as good as Rust enums. But I guess it's possible to emulate that with an array of variants...
4:40, you can store anything in a std::map (it's a template class afterall). However, it's infamous, because it seems that it's implemented as a linked list, which is a fairly slow data struct. So often someone is seen implementing his own version of it (which I recommend, btw).
4:51, did you just swallow a bank card?
5:18, this prevents an annoying potential error from fast C initializations.
5:24, it's really bad. I don't use, neither recommend. 5:37, they are deactivated for f()s marked as noexcept.
6:57, it's a hint for the compiler, to optimize the code: it's likely to happen this than the other alternatives.
And those 'this->' are unnecessary: by default the class variables are used; there's no ambiguity.
7:40, with old style enums, I like to reduce this kind of code to 3-5 lines, by creating sorted arrays, searching the char in 1, via some algorithm, and using the resulting index on the other. It's as fast as elegant.
8:20, this is just a reference for a class object that lives on the stack memory, meaning it's not allocated by the user. This token may be string or a char at a time. std::string makes automatic allocations, so it'll free its memory for you. The reference works as the borrow from Rust, I guess. What changes from it in this f() will remain after.
std::maps are not implemented as a linked list, they're a red-black tree (I suppose AVL might work as well, but red-black is the most common).
exceptions are fantastic and very much recommended. In some cases mandatory (such as here).
[[likely]] and [[unlikely]] just order the code so that the "most" likely branch is the closest to the conditional jump. But it is not as good as it seems for modern CPUs (like < 15 years old lol)
Why do we nede this-> ? Just referring to the member variable doesn't work?
I always do the member variables that are private up top, first. Because these, _always_ turn out to be the most interesting and crucial things.
Think of it as attempting to look like Rust, without actually being good, like Rust.
So you are saying that you think implementation details are more important than public API? Really?
@@vladimirkraus1438 For application development - absolutely. For library development; no.
@@vladimirkraus1438 You can think of it like this; a public API is only ever important in library development. It needs to be stable, it needs to produce the same outputs, no matter it's internal details (which can change under the end-user's feet from version to version, but still produce the same output).
This is where public API is of import. For application development, like say, a debugger like GDB or RR; why would the public API be the most important? They're not libraries. What's most important is their functionality, their performance. Any change to the underlying system _will_ be changes to the implementation details.
This means, what data structures are being used, what is the layout, when are they read and written, so on and so forth. These are all implementation details. Rust was *brilliant* when they realized this and separated the declaration & definition of a type and it's implementation, or behavior.
@@simonfarre4907 Why do you distinguish writing library or application? All is just a code. And if implementation details are the most important to you, then you are doing something wrong.
Btw. how are you ordering the methods? Private ones first at the top too? Then protected ones and public ones at the end?
@@vladimirkraus1438 False. Application development and library development differ *massively* in what requirements and constraints are placed on both the developer and end product.
The ordering of methods is irrelevant, but after the definition of the actual data type, I proceed like usual, public first. There's plenty of applications that you use *right now* that do what I've said here.
That lexer::read_char() method is really bad: A void argument, pointless usages of this, using += 1 instead of ++, a pointless return, etc. It's really weird.
EDIT: It looks like the constructor also makes an unnecessary copy of the input string.
make a pr?
@@TheVimeagen I made a PR.
private before public is in alphabetic order.
Rust also has something like [[unlikely]] with the function attribute #[cold].
would be cool if [[ likely ]] was used to tell the compiler which branch is "more likely" (duh), so that it performs sort of a hotspot optimization like JITs do, analyzing the runtime. Just that in this case there is no analysis of course, but it assumes the programmer's directive is reflective of runtime behavior. I am really afraid JITs will get significantly better than AOT compilers, and that I'll be forced to migrate to Java 🤮.
most iconic SWE duo
Would love to know the color scheme? 😅
Wow, Is like seen two old guys just talking about C99 :D
Come on!!! You streamer guys should try to make a tetris in C++23, and will see THE POWER of: Spending a week to make a Cmake, Spend another week to try to add Conan/vcpkg, try to install SDL2/raylib. Then after a month, you will get beard, so you can now, Spend another week why cmake does not recognize SDL2/Raylib. And now, after two months, you are ready to start coding C++23. Thats how to do it :D
bros, LLVM have their own APT repos
which plugin was that 6:33
someone know what is the color scheme?
Dude it is rose-pine!
The only reason why most programmers choose to define private member before public is to access private member variable in public function. Otherwise cpp compiler starts to puke 🤮 right back to you. It’s like I don’t recognise it, who is he/she ahhhhhhhhhhhhhhh😂
I’ve seen alternating private/public/private/public with members first, and then the methods.
@@TehKarmalizer I do that. It looks horrible but it helps my ADHD brain keep things organized lol
Variant on a string, char is dumb. String has an internal variant on its pointer for use with the small string optimization at least on most standard library implementations. So doing that yourself is likely useless
… the c++ standard library is a pain to look at. It’s worse than the code in the Boost libraries.
It's not meant to be looked at, it's meant to be portable. The "pain to look at" is literally a carefully guarded feature.
trash dev catching strays again 😂
every stream
Rust - -
rofl xD
C++ > Rust.
The fact is that CPP is the C pre-processor, which is the true pinnacle of elegance. C++ tried to beat the pre-processor's advanced feature set, failed miserably, and stole the name out of spite.
cringe
@@isodoubIet sad to hear about your condition
@@JuusoAlasuutari Didn't realize vegetables were capable of emotion
@@isodoubIet happy to hear about your realization
terrible c++ code
like your mom wrote it.
@@musdevfrog no, my Mom's a significantly better c++ programmer than the monkey who wrote that, and she's never coded c++.
What's wrong with this? Looks quite good to me. I am no C++ programmer though
@@jarvenpaajani8105 I'm not a developer, but the code does look peculiar. 'return's at the end of functions returning void; underscored members with "this" everywhere, "next_token" could be much shorter, say,
try {t.type = type_from_char.at(byte_); t.literal = byte_; } catch (const std::out_of_range& unknown) {.. deal with it.. } read_char();
where type_from_char is a respective map.
Some stuff, such as 'final's, [[likely]], std::variant, I don't understand why it's needed, but, again, not a developer, maybe it improves performance.
The biggest problem is a lack of familiarity with the type system. Using a string is making copies of things everywhere. Using string view means the buffer exists once and then everyone has references into that same buffer. Many cases where this program would benefit from type parameters.
You don't have to nag about something that you don't understand. Just a waste of time.
tom modCheck
tom tom
std::for_each(comment.begin(), comment.end(), [](const char c) { std::cout
@lepingouindefeu Thank god I use C# now lol (I’ve become a product of Microsoft)