Comments by "MrAbrazildo" (@MrAbrazildo) on "ThePrimeTime" channel.

  1. 18
  2. 15
  3. 11
  4. 7:08, in old hardware, the engine instructions/data didn't fit entirely on the cache. So, depending on how many instructions an action takes, CPU had to seek the RAM, which uses to be 100x slower (maybe less in a console). On modern hardware, all instructions/data are in the cache, which has much more memory than they require, for an old game. However, RAM is still used even nowadays, for multimedia stuff: images, video, audio, textures and other more than 64 KB sized. The optimization for these large things targets to load part of the RAM on the VRAM (GPU cache memory), in a moment the user doesn't care, like a loading scene - i.e. God of War's Kratos passing through some rocks. Sometimes this is used for loading from files to RAM too. 11:58, but he is doing it for modern hardware, isn't he? The video's goal is just to explain why Quake's alg. is not meant for all cases. 13:00, the sad truth is that these pointer transformations are UB (undefined behavour). That's why the guy commented it as "evil": he just wanted to get his job done, leaving the comment for the future masochist who will deal with the potential nasty bug. UB means the operation is not standardized. So, the app may someday start crashing or giving wrong values (out of nowhere!), if any thing change from the original setup: hardware, OS, any imaginable protocol that interacts to the game. Not even old C had an expected action for that, as long as I heard. 13:52, in math, a minus exponent means that the number is divided. So, x*0.5 == x / 2 == x*2^(-1). Instead of multiplying the whole number, it's possible to change its exponent, by sum or subtraction, which are faster operations.
    11
  5. 9
  6. 8
  7. 7
  8. 6
  9. 6
  10. 5
  11. 4
  12. 4
  13. 4
  14. 4
  15. 4
  16. 3
  17. 3
  18. 3
  19. 3
  20. 3
  21. 3
  22. 2
  23. 2
  24. 2
  25. 2
  26. 2
  27. 2
  28. 2
  29. 2
  30. 1:07, by that do you mean "data racing" (more than 1 thread writing the same data, at the same time) ? This is easily solved since C++11, with STL <atomic> library, at compile time. The remaining issue is the "false sharing": when you have different threads changing different memories from the same cache line. So when 1 write at its portion, it "freezes" the entire cache line, not allowing the other thread to write, during that brief moment. This is a performance issue, not a bug. It's still solved by hand, having to align the data, leaving each thread to its own cache line. 1:24, what exactly Rust solves here? Those pointers are meant to acquire an opened resource, freeing it later automatically. A common C++ skill issue here is to use those pointers for data that could easily fit the caches. Since people are used to call 'new' in other languages, in C++ it'll get that memory far away, on RAM or an even worse place, becoming at least 100x slower, unless the compiler saves the junior dev. Why C++ made life harder on that? That's because it actually made life easier: it assumes 1 wants the data on cache, thus by default it dismisses us from even having to use 'new'. 1:55, I don't know about unique_ptr. But what I know and saw, more than 1x, is that compiler is smart enough to put an entire std::vector on a cache. Assuming unique_ptr is part of it, it's prone to be free too. But of course, it depends of the memory it's holding: if it exceeds the caches sizes, it'll stay on RAM. I think there's nothing Rust can do about it. 17:12, I thought he would say that C's pointers are the same concept from Assembly. Now I'm confused, since I don't deal with it for a long time. C++ iterators do some compile time checks, while pretty much the same speed.
    2
  31. 2
  32. 2
  33. 2
  34. 2
  35. 2
  36. 2
  37. 2
  38. 2
  39. 2
  40. 2
  41. 2
  42. 2
  43. 2
  44. 2
  45. 1:38, I used to dislike #ifdefs. Nowadays, I think they are quite nice, because they help to debug. For instance, if a block of code won't be used in some compilation, that code won't actually exist, even raising a compile error in case some piece is missing. So this is already a check, a confrontation vs what the coder is thinking. And it's possible to keep switching the switches, getting a quick statistic effect about any bug. Codeblocks IDE can "blur" blocks not targeted to compile, a pretty nice visual effect. 4:19, I agree with you, because people use to think that Single Responsibility Principle is technically only 1 thing, but I think it may be semantically, instead of technically. So a f() may do several small/tiny technical things, to achieve 1 goal. This way, outside the f(), the rest of the project can look at that f(), thinking about that goal only. It's already an isolated functionality, despite the fact it takes more actions internally. 4:31, I completely disagree here. I already wrote tons of words in a video of his, uploaded by other channel. If someone is interested, I may write some words here too. 6:28, sorry, dude, we are waiting for Carbon. If only it changes that bad syntax... 14:35, I think this is much more important than it looks. I can't prove it, but I feel like spending more energy when travelling vertically. So this should be avoided, whenever convenient. 18:02, I personally omit { }, because I love compact code. But I wouldn't argue vs this standard. I would put them on the same line, though. 18:21, in C/C++ the if isn't ruined by comment, even without { }.
    1
  46. 1
  47. 1
  48. 1
  49. 1
  50. 1
  51. 1
  52. 1
  53. 1
  54. 1
  55. 1
  56. 5:31, I don't know what is so bad about C++, like these kind of people use to say. It has all those types you mentioned, like classes that are not defined at compile time (interfaces), "simple structs with methods" (classes), foreach as algorithm and in the language core too, optional types, and so on. 7:15, C++ has the optional type, for a type that may be valid. But has a better solution, if 1 adds the GSL library, the NotNullPtr class (or some name alike), providing Zig's not nullable. It's also possible to develop your own pointer like that, and it doesn't take much longer. 7:49, so there's no C++'s namespace on those languages, huh? It works like a surname for a library. Each 1 has its own, so name conflicts never happen. It's also possible to dismiss yourself from typing that all the time, if you are sure it won't conflict. 8:51, copied from C++, which also has them as default parameter, meaning that 1 doesn't need to explicitly send them on initialization. 9:00, and if "you forget to clean things up", it'll do that for you, no messages needed. 10:05, it means 1 doesn't even need to do a deallocation. 11:20, people are contradictory: they love the "error or variable", for a variable, but at the same time they are afraid of "NULL or a pointer", for a pointer! What's the logic?! 18:57, yes, undefined means it'll initialize that memory taking the "trash values" left in there, from other variables previously freed (no longer exist). C/C++ has this by default (faster initialization), meaning 1 doesn't need to lose time typing ' = undefined'. 23:26, actually it's much better, because it's supposed to has several public f()s, only asking you to type 'public' keyword 1 time only! 23:43, 1) f() is not attributing something necessarily, so there's no need for the = operator. 2) f() doesn't know what the user will do with the variable (changing it, for instance). That's why it doesn't use to be 'const', although it's possible in C++, just not recommended. 3) Specifying the returning type improves compilation time. In my experience, it's better/safer to declare it automatic (fn, var, auto, depending on the language) during development, and switching it to its explicit type, after finished. 27:08, C++ is smoother, dismissing you from typing usize, deducting sum and the returning type as int, due to its default for integer literals.
    1
  57. 1
  58. 1
  59. 1
  60. 1
  61. 1
  62. 1
  63. 1
  64. 3:30, abstractions are the best thing, but can also turn back against the dev. For C++, I take a FP approach by default, until some variable can cause too much damage, if changed wrongly or from a wrong place. Then it goes to a class, to control everything about it. I 1st start with free things, then tie down some critical things - "decoupling" is not welcomed for those cases. So my code has many more free f()s than classes. Complexity is not inside only 1 f(). If it's certain that a bug is inside 1 f(), it's just a matter of (short) time to be solved, doesn't matter how complex that f() is. It's like a lion trapped in a cage: just study it, and tame the issue. The nightmare happens when 1 needs to travel throughout the project f()s, searching where it might had started. This is the main reason to write classes to restrict who can change critical data. Let's say someone is coding a football (soccer) game. It could has a class for ball, players/actors. To coordinate when a goal is made, and its consequences, changing variables in more than 1 class, I use to have a class to tie those things together. It could be called referee. So public Referee::verify_and_change_if_goal would be the only or few f()s allowed to call private f()s Ball::goal_restart (to put ball in the middle of the field) and Player::goal_restart (to put players into their half of field, in certain locations, with some random variance towards that location, to seems more realistic, less robotic) . So that Referee public f() can change the world, from any point where its object appears. Bad design! Actually, no. The verifications would be made inside Referee (lion in the cage), only changing variables in case of goal. So doesn't matter if it's called several times, even by mistake: the worst possible thing is to loose some performance; it won't ever bug the game. It doesn't even matter if the code grows up to 1 billion LoC: those things will continue locked. But let's imagine the worst scenario: some internal error happened inside this chain of calls, and junior dev decided to shortcut it, creating his own way to change variables after the goal: 1) He would get compile errors because, let's say, the main f() who calls the Referee public f(), and now is calling junior's, is not 'friend' of those classes. Junior turnaround it: 2) made the main f() friend of all those classes, so that he can write his own way. On the next error, some senior will see the class definition, and think: "Wait: why main is friend?!" . But let's make it more complex. Instead of that, junior: 3) pulled Ball::goal_restart and Player::goal_restart to public area. A senior may think those were always public. This is awkward, because some error might happen, by calling 1 f() and not the other (i.e: Ball's but not Player's), since they are now decoupled. But this could be avoided, if they had comments on their classes declarations: DO NOT MADE THIS PUBLIC! 4) Junior rebels: made all the classes public, deleting those comments. FP rules once again! The security system is now completely destroyed! Well, senior devs should suspect all is wrong: 'everything public' is the sum of all fears!
    1
  65. 1
  66. 1
  67. 1
  68. 1
  69. 1
  70. 19:05, still about this code. Its returning value is inverted, considering that 0 is false, otherwise true, converted to int as 1. This is in the core of C/++. So this can (and will) cause bugs. I can imagine: if (test()), hoping that the test passed, when it got a NULL! And UB! Ok, sanitizer would get this fast. But let's not be bad programmers just because of that, shall we? I know that, in ancient C, the 'main' f() got this absurd convention for some reason. And someone could say that this 'test' was made an "entry point", thus trying to follow main convention. But 1st, (at least) C++ has EXIT_SUCCESS/FAILURE, to let us forget about this. 2nd, I'll assume this was just a no-excuses mistake. So, how to fix it? It's not possible to just exchange those values, since bugs would start to poping-up. If I would alone in this project, I would just create a C-enum, like: enum test_ret { TEST_FAIL=0, TEST_PASS }; (The explicit 0 is due to 1 had once be the default. So I don't trust enum defaults) . The important thing is to tie the failure to 0 (false). This would be enough, since I respect global constants. Not just because it's a C++ Core Guidelines rule, but also because I have personal experience about that. People underestimate literal numbers danger. However, working in a team, it'd has people writing things like: if (test() == 0), and the enum would be implicitly converted to int, generating bugs, if nobody hunt those call and change them by hand. It's what I would do, after the enum. If they were too many, risking the team write more of them than I could fix, I would change the enum to 'enum class'. It'd cancel the implicit conversions to int, causing compile errors. So people would be forced to see the enum class declaration, and its global constants - any IDE would open its file and location. Even so, there would be people just taking a glance at it, thinking "Ah, some idiot changed it to enum class, thinking it'll make any difference" . So if I start to see many casts to int, like if (0 == (int) test()), the issue still would not be solved. Then a more drastic solution should be taken. I would change the 'int' returning type of test to something not declared before: CALLING_A_MEETING_TO_REASON_ABOUT_THE_STUPID_TEST_RETURNING_VALUES. Compile errors popping up. The idea would be to stop the entire production line, making the new-feature-addicted boss freaking out, risking my job. But it should be made before this gets out of hand - some decision of not messing with working code. To get the boss hallucinating, could even put the time: MEETING_AT_10_30. He would appear sweating, pointing me a knife: "Guess what? Nobody steals my job!" "I don't give a crap about your sh##y job. I'm paid to defend the company goals, which are above you. So I'll keep that, until a get done with this sh##ness, and quit to wash dishes, which is a better job, thus paying more!"
    1
  71. 1
  72. 1
  73. 1
  74. 1
  75. 1
  76. 1
  77. 1
  78. 2:09, 1 of my biggest bugs in life came when I thought: "3 lines for these 4 ternaries... I guess I will wipe this into 2, elegantly" . I reviewed it by mind and... approved! Those lines held about 8 possible combinations. It happened that 1 or 2 of them were wrong, well disguised among the others. So those use to seldom happen. To get things worse, there were 2 other parts of the code that were more suspicious of being guilt, so I took a while looking closely to them. Automated tests would had get that easily. 3:40, I guess there was code before and after that break. The problem is that in C/C++ 'break' jumps out from blocks of switch, for, while, do-while , but doesn't has this power over if/else ones, as the coder unconsciously thought at that specific moment. So the break was applied to the 1st block above those ifs: the switch, jumping over the processing of incoming message. I once got a bug from this. I never wrote a tool for this 1, since it never was a recurring 1. For this AT&T there were some solutions to replace the else-block, trying to not duplicate the code to where it should jump: - Make it a f(). Bad design, since the rest of the project would see it, and may call it by accident. So boilerplate code should be added, to remediate this. - Make it a macro f(). Despite I don't use to have problem with macros, I agree that it would be noisy/dirty code, depending on its size. - Use a label after the END IF, to be reached via goto. Better, but this goto could still be called from any place from this case, at least. - Lambda f(). I think this is the best 1: break would result in compile error, and return from any place would exit at the right spot. However, this was C, and neither C++ had lambdas at that time.
    1
  79. 1
  80. 1
  81. 1
  82. 1
  83. 1
  84. 1
  85. 1
  86. 1
  87. 1
  88. 1
  89. 1
  90. 1
  91. 1
  92. 1
  93. 1
  94. 1
  95. 1
  96. The author showed good skills, regarding clean and DRY code, automated style (including safety checkings) and knowledge of technicalities about C. But to me what really matters about senior or ace worker is the concern toward safety. He didn't mention this, at least not by words. 19:05, for instance, in this code I would point out some things: - 1st, I would "rewrite everything in Rust"... Nah, it'd be in C++, which is indeed an improvement over C, not lefting anything behind. If the boss didn't agree: https://www.youtube.com/watch?v=O5Kqjvcvr7M&t=22s - I would pay attention if linked list would be the best choice. It's only faster when there are too many insertions in the middle - that means a sorted list, somehow. Otherwise, a std::vector-like is much, much faster. For instance, if it's just a database, this sorted linked list would be slower than an unsorted vector-like, adding to the end, and removing in the middle by replacing it by the last. Or am I wrong? - I would study the idea of changing that boss raw ptr to a unique_ptr or something higher-level: more elegant and safer. - I would change that person::name, from C-array to std::string: more comfortable to work and almost no chance for UB, leading to cleaner code, since it'd require much less if-checkings by the user. But main advantage is that std::string is not a primary type (it's a class instead). So it's possible to later change it, by a user-defined faster container, keeping the same syntax to communicate to outside - no tons of time refactoring throughout the entire project . This would not be achievable with C-array, unless all its uses/calls where made via f() or macro - which nobody uses to do for it. And would worry about that only if that std::string was a bottleneck, which is unlikely. But ok, let's imagine the worst scenario: it needs to be replaced by a fixed-size array, which uses to be 20% faster on the heap, only. Since it is not flexible as a std::string, does that mean it'd break its syntax, needing refactoring? Actually no, there's a turnaround: a tiny user-defined class, inheriting std::array (same speed as a C 1), and writing by hand all std::string specific functionalities, like += for concatenating. So all the work would stay inside the class. In case of bigger name being assigned to 'name', an internal check would be made, as Prime pointed out. But not via assert, which would break the app - it could be 1 of those ongoing apps. Just an if, truncating the name to the size, writing an error to std::cerr. But probably a fixed-size array could not be used: it has a limit of total memory per app. Since this code is making allocations, it suggests there are a huge number of persons. So it'd get a seg. fault. So std::string would be indeed the best choice.
    1
  97. 1
  98. 1
  99. 1
  100. 1
  101. 7:07, I think C++ fits in this article even better than C#. However, it requires the user to develop "some feelings", if he chooses to use default behavior/resources, instead of developing his own defensive tools. For instance, my 1st thought after certain action(s) are set to: - Check the result of a f() or algorithm right away. - If what matters is index where a pointer stopped after an algorithm, I get rid of that pointer at once. - For things that I'm used too, I write as fast as I can (favouring productivity). When something starts to be unique, I proportionally start to get slower and more reflexive about (favouring defensiveness). When things get complex or I'm failing often (for whatever reason), I stop everything to write a tool to lock the right behavior forever, going back to be faster/productive. So things go inside f() or classes (yes, including setters as nonpublic) , only 1/few way(s) to reach them, putting as many layers of protection as needed (thanks to C++ high functionality), whatever needed to reach productivity once again, because I value doing things without thinking twice, in crazy fast typing fashion. So C++ fits in this article purpose of proportionality production, according to its complexity. An example: I was working from picking values from a string, by pairs. I decided to use its default behaviors. I wrote fast, everything works predictably. No need for fancy tools nor languages. Then I decided to optimized it, by using a pointer that made 2 steps per cycle. It got expressively faster. It was also fast to develop, and worked flawlessly. So I left the computer, with my 15487th easy victory using C++. But my gut feeling said to me that I wrote too fast something that I'm not used to. So, calmly drinking a coffee, I made a brief reflection about it. Mentally I discovered that the 1st step from the pointer was immediately checked, as I use to do, but not the 2nd. So it would step beyond array boundaries on the last 1, in some cases, whenever the f() didn't return before. Easy check, easy fix, I just added 1 line check for that.
    1
  102. 1
  103. 1
  104. 1
  105. 1
  106. 1
  107. 1
  108. 1
  109. 1
  110. 1
  111. 1
  112. 1
  113. 1
  114. 1
  115. 1
  116. 29:24, I agree, but I would never rewrite STL because of that. My f()s and classes use to not be generic, whenever possible. For things coming from STL, I use typedefs. 30:15, this is a Java thing. Well coded C++ use friend keyword, to allow just a few f()s to access nonpublic data. 31:43, std::stringstream is just a higher level scanf. And faster, according to a measure I took. I would only argue against it if performance was at stake on it: pointer, for instance, is much faster. It's syntax is not ugly to me. And std::to_string does the trick, if this is the only reason of using this stream. 32:25, 1 line f() could fit inside the class definition. And using std::clog, to not be generic, would dismiss receiving ostream and another class too. Result: auto show_val ( ) {return std::clog << val;} Plus, even using the overload, it could be made via: std::clog << "blablabla" << object.get_val(); I think the code in the video is beautiful, if 1 desires what it offers: throw strictly the object (not 1 of its members) to an object that is or inherited a std::ostream. What is stupid and ugly is to write a f() like that (in the video), when 1 would be satisfied with the already existing 'std::ostream::operator << (int)'. And printf is better only when several values are being read at once - otherwise it's less productive, due to type specifying ( + warnings) and demanding more typing on keyboard. So their thesis of condemning streams fell flat. 35:25, I have a tolerance about 1 line f() definition (below its header) inside class definition, because I can still put { } on the same line. For more than this, if { } is used normally, it starts to push the code downwards, looking noisy to me. I also try to align returns, f() names, 'this' specifier, and f() definition, whenever they are "attached" (1 right below the other). Of course, I don't put these many stupid unnecessary spaces between the "fields". (35:39, giggles) About the horizontal look, it's ideal for eyes, since they are widescreen. Code is ideally meant to be looked by eyes, not with help of hands, unnecessarily travelling vertically.
    1
  117. 1
  118. 1
  119. 1
  120. 1:28, to avoid forget to close (, {, [, I kept the option of automatic closing them, right after I opened them. The same for return instruction: whenever I start writing the f() header, I put the return right away. Other solution is to declare the returning type (at left of the header) as auto. 1:30, I only use C-array when it's const an already initialized with values, that I'll access through enums. At this case only, it provides advantage over C++'s, due to shorter notation. Otherwise, I always use this last 1. 2:11, this includes building defensive tools, to make it safer, far from its default. 3:20, I'm learning to use MQL5 for finance, and they use a C++ inspired language, more defensive by default. I also heard about some people using Java, to make often changes with less risk. But I heard too, in a presentation, that "C++ is the language of choice on this subject" . 5:50, once 1 get used to those, they become easy to manage. If bug happens, it's no more hard to find. 8:53, I heard C++ is starting to replace C on there too. 11:47, since I don't like to configure compilers to attach them to code editors, I only recently got the complete C++17. In Linux, I finally get C++20. Android is barely at C++14, at least with SDL dialog to Java JNI. The good news is that, to become massively more productive, just C++11 is needed, and, for a few blasting features, C++14. C++17 (and I guess C++23 too) is weak, but for C++20 it's said that it's the new "changed our way of coding" . 12:57, it abstracts the low level by default, it's middle-level, and can jump easily to high level, if 1 develops his own classes, working exactly the way he wants and the project demands. 13:03, all of this from C++11. Lambdas are nice: I just type [ ( { (, and end up with [ ]( ){ }( ), completed by the IDE, which is the hard part. Or I can just type lbd + Crtl-J, Codeblocks will expand this according to an abbreviation I previously wrote, that could be like that 1 in the video. To avoid conflicts vs the capture, just type [&], and it'll capture everything as a mutable reference, also dismissing having to receive f() arguments.
    1
  121. 14:08, there's a way to get rid of all those if checks, safe and easy that even C can handle: get a file only to handle the nodes chain/tree. There, some private content will handle the control over the nodes. For public access, only public setters. (This is 1 of the cases this FP approach can enjoy the same safety level from OO. It only evens because those setters are supposed to be called from anywhere) . So the use would be like: create_node(); // Let's assume it failed. Optionally, an err msg could go to some log output. goto_next_node(); // It automatically checks the next 1's validity, thus doing nothing. Another err msg to log. int a = read_var_A(); // Validity check is made here too. Since there wasn't a "next node", it'd return variable from the current 1. But since the list is empty (automatically checked too), a literal value would return. Log should report all of this. goto_previous_node(); // There's none, so it'd not go anywhere. So, this is pretty safe. Log could even get better, by using a trick I saw in a Eskil Steenberg's video: each of these f()s could be a macro call to their actual versions. i.e.: create_node_ (__FILE__, __LINE__); // It'd call this behind the scene. By reporting the current (at the calling moment) _FILE_ and _LINE_ to log, it'd not matter how large the project would be, the exact location of the error or missing explicit check by the user would instantly be known, making debugging tremendously easy. The price is that, despite the code would be clean of explicit checkings, the generated bytecode would has lots of implicit checkings, leading to much more branches, thus slower code. But this is easily fixed: once the app is safe, follow log's instructions about locations, adding explicit ones. Once no more of those err msgs appear, just change 1 macro line, which controls whether or not the implicit checkings are compiled. And then recompile the whole thing.
    1
  122. 1
  123. 1
  124. 4:13, this is strange. If it's maintainable, it means above all things that it hasn't UB (thanks mostly to the high level language you are using) and that it's not changing variables on wrong places as well right places too, in different f()s (well structured). But if it's good on this, it should be prone to be readable too. If I would risk a guess, I would say some variables are been changing on wrong places only, damaging the meaning of things (strategic view). 5:43, this may endorse what I'm saying: the goals of each f() were properly defined: they are mixed. Some f()s are doing job of other, when they shouldn't. 6:05, my "giant" f()s use to be 5 screen sized. They have a preparation of data (that only makes sense if used internally) to be used later, so this takes space. Strategically, it's easy to see: preparation 1st (2-3 screens, let's say), a main loop later, processing them. I keep things inside the f() due to encapsulation: I don't want the rest of the project having direct access to that functionality, since it would not make sense. But if the f() was long enough, making me start to forget what was done earlier, even strategically, then I would start chopping it. I would make a class, having the f() as public, and several other f()s private, as its internal content. No way I would let it with 200 lines, if I would start to loose understanding over it. 6:23, the problem with FP is that it's too optimistic about its safety. For example, to work with multimedia I like to use SDL2. It's pure FP. So I get some of those things, that no alien should mess with, and I put in classes. So I think in case of confrontation, OO should force FP to adapt, because it brings issues to code safety.
    1
  125. 1
  126. 1
  127. 1
  128. 1
  129. 1
  130. 1
  131. 1
  132. 1
  133. 1
  134. 1
  135. 1
  136. 1
  137. 1
  138. 1
  139. 1
  140. 1
  141. 1
  142. 22:36, C++11 also had for range loops: for (myType &value: vector_of_type). But I kept using the old for, through a macro like: myfor (it, vector_of_type), because I felt counter-productive to have to specify the type used by the container. I only embraced for range loops in C++14: for (auto &value: vector_of_type), using auto to kill that faulty feature. 25:02, I disagree, because these things are keeping themselves generic enough to work with any type. And everything is separated by scopes, that's why so many :: operators. C++ even has a kind of dry syntax, compared to how many things it's handling on those libs. 1 has to compare beauty to what it's trying to achieve. 27:00, macro can clean this code. While the lib has it in its generic form, to hold all possible user configs (and I think it's beautiful, because it achieves that, with likely minimal syntax) , the user don't need to do that. If it happens to has several arguments, just make a macro or typedef about its meaning: #define ParamsForHashDecl typename KeyType, typename ValType, // ... the rest. #define ParamsForHash KeyType, ValType, // ... all the rest. using ProjectsOnlyHash = HashTable <ParamsForHash>; // Alias. Since this is made only 1x, similar f()s headers would be like: template <ParamsForHashDecl> const ValType &ProjectsOnlyHash::getValue (const KeyType key) const; getValue is a function from the class 'ProjectsOnlyHash' (1 doesn't even need to read its template args) , that receives a KeyType (that can't be changed) and returns a reference of some ValType, which can't be changed either. The f() also can't modify data from the ProjectsOnlyHash class, except those declared as 'mutable'. At any time throughout the project, if the user wants to remember what is ProjectsOnlyHash, or its template parameters, just leave the mouse over the respective word.
    1
  143. 1
  144. 1
  145. 1
  146. 1
  147. 1
  148. 1
  149. 1
  150. 8:35, I agree. But I don't know if debugger is overkill. I use to make unity tests. Maybe some prints. This solves +95% of everything. If bug persists, which is rare, I also use a technique I created, called "hacking the solution": I change the code a little bit, test, see results. Then put things back, repeating the process in a different way. This puzzle points me to the right direction. 10:02, I do that too. I think TDD is a bit invasive, when I'm developing the f() signature: I still don't know exactly what it should receive/return, so I want a bit of a freedom. As soon as this is established, I write the tests. Once both are made, the rest of the f() development/fix can reach a pretty fast speed, as it becomes oriented by the tests. 10:10, but I never delete test, unless it can be replaced by 1 that tests what it intended to, in a more edge case way. C/C++ also allow conditional compilations, including or not the tests. So their presence can be configured by changing just 1 line. 17:27, the same thing happens with all those asserts: if #define NDEBUG 1x before, all of them suddenly disappear. So the programmer is not condemned to their presence. 17:53, and compilers evolved too. I saw more than once std::vector (variable length size array) being faster than a fixed size 1! 19:30, it's possible to write tests that just emit reports/logs, showing errors, but not shutting down things. 20:18, I'm too. But I'm 2 workplaces on Linux because, when in development environment, I don't want other minimized windows annoying me, from the rest workplace. I also use the Cube, to give a nice effect when switching between them. 24:40, 1 of the reasons why I use Codeblocks IDE is that because, either on Windows or Linux, I just install (1-2 minutes), pass my pre-configured archive (some minutes maximum), and I'm already coding, with everything I want.
    1
  151. 1:10, does anybody can explain to me what's the logic in calling enum a "sum type"? What's a sum type btw? 1:26, isn't std::tuple enough? 1:53, it's more comfortable than structs, due to possible accessing members via indexation. But I don't use it, because it generates too much assembly code, which leads to slower code. 2:00, are you sure? I know that acquiring/freeing resource are, and this is the main (and should be the only) goal for smart pointers. But raw pointers/iterators are pretty fast for memory accessing. All STL algorithms demand to use them (a few times as references). 4:58, but I'm, and I say it's the best language for crafting tools (more functionality/freedom). Whenever I face something risky around the corner, I build a tool to deal with that. 6:50, you can put declarations and definitions on the same header. I do this for small projects. 7:25, there's no issue about the private data being on the header file. It'll continue being forbidden for public access, unless otherwise expressed. 8:55, I rarely forget to type const. But I agree that const by default is useful. However, it's possible to create a convention for the team: 1) Create some prefix/suffix meaning const for the type, like 'int_'. 2) Do the same for 'mut'. 3) Config. to highlight them in the code editor. 4) Config. to NOT highlight the conventional types in the code editor. This way, the team will notice, at once, that something is wrong when they type 'int', and it doesn't highlight. 10:20, I think const_cast is always a mistake. There's an optimization the compiler does, by exchanging all const by literals, since the beginning, which might colide to that. Better is to go right away to the f(), fixing its const-less issue.
    1
  152. 10:45, I guess the maintainers just opted for keeping the non-const iterator because it's a clue, of what the iterator will do inside the f(). 11:53, "Move was a mistake. It should not be standardized. The compiler can see the allocators" - said a compiler maintainer. It can manage the movable things for us. 12:20, std::function is not mandatory. I would never use a runtime exception just to have it. 1 can use pointer to f(). Its only disadvantage over that class is its kind of annoying type syntax declaration, that can get worse. However, there are some turnarounds: a) auto function = my_func_name; // By omitting the ( ), it's already a pointer to f() . b) If receiving it in a template f(), doesn't even need to know what the type is: just pass the f() name, when calling the template f(). c) If it's not a template, and needs to know the type to declare it, just type another thing, not convertible: the compiler will halt, saying the type it deducted. Then copy/paste it right from the log. 12:42, as I said before, write a tool. For instance, why not put a printf on copy constructor? It'd warn you. And non-primitive types can be send by copy. 1) Compiler may arrange things for you. 2) The size is more relevant than the type. For instance, I once had a 1-byte class. It had its f()s, but only 1-byte data. Passing it by copy on the entire project wasn't slower. 13:25, I don't think these are valid issues. It's just a call to a f(), when returning. If that's a concern, 1 can keep those values in a struct, dismissing calling f()s at returning. These "out parameters" may have a performance cost.
    1
  153. 1
  154. 1
  155. 1:36, I think that would better fit as definition of productivity. I think maintainability is more related to not mess what was already done, whenever you need to change part of it, either by making new feature, refactoring, upgrading, optimizing, exchanging strategy, and so on. 1:53, I don't have this issue. I may forget a bit about how things communicate between themselves, but I get the idea from some minutes to some hours. I may also change the style a little bit. What's the secret for this? I don't know exactly, but I keep the code commented in every tiny thing suspected to cause problems in the future. And I keep documentation about the project as a whole, outside the code too, for both strategy and tactic approaches. 3:35, this shouldn't be happening. Despite it's impossible to know how the entire project works tactically (the exact variables and how they change) , it's viable to have both the strategy (how it works at all, high level) of overall data structs, how they communicate and their goals; and the tactics inside each f(). For this last 1, I use to write its goal, comments in almost every line and even 1 or 2 edge examples, in case of a complex f(). I discover this need by making a brief "brainstorm" about it, mainly focusing on the future readability. This is 1 of the key reasons why I have a "spaghetti" style of horizontal code, putting comments at its right side. People take this as ugly and unreadable at 1st glance. But I think it's compact (most of my f()s can be seen in 1 screen) , encapsulated (no unnecessary extra f()s are made, just for the sake of been "readable") , and commented enough for the future, without pushing the code downwards (which I think it's awful and damages readability) .
    1
  156. 1
  157. 1
  158. 1
  159. 1
  160. 1
  161. 1
  162. 1
  163. 1
  164. 1