What should I cover next in the C++ series? Let me know below 👇 You can try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/TheCherno . You’ll also get 20% off an annual premium subscription.
Hi, I agree with the "optional unwrap" being a conversion, but the is ready seems like ... well ... it doesn't seem like a conversion but more like a query. Basically you use the same language to describe to semantically different operations. await vs query. What would happen if he thing you are awaiting (the mesh here) would be convertible to bool as well. Should the compiler pick the longer conversion chain or not? If it's not ambiguous it is something you would never remember as an avarage c++ programmer. (At work we use !!some_var to force the bool conversion, I don't like that either tho 😀)
I've used conversion operators for something similar to this. Libraries I'm currently using are Raylib, ReactPhysics3D, and Recast Navigation. All three have their own versions of Vector2/3, Matrix and Quaternion. Most of the time they are the exact same, a Vector3 is usually just three floats in a struct for example... but having my own maths library that has conversion operators to all three libraries has helped massively. Before using this approach, I was having to take an RP3D Vector3 for the physics object position and convert it to a Raylib Vector3D to update the object's position (for rendering) and to RecastNav Vector3 for navigation.
I literally spent a day on debugging this at work two days ago. I was doing a rewrite of our logging system, because the old one sufferred from deadlocks and memory errors. In a few instances, there were logs which logged 8 different variables. Most of the variables were custom types, even though they really represented primitive types, such as int8_t, int16_t, etc. And those custom types also had overloaded conversion operators to those original types. They were formatted via fmt::format, but since we are already using C++20, we agreed to migrate all fmt instances to std::format. In the editor, the code behaved like all was well, but when I tried to compile it, I got several pages worth of an error log per call of that 8-variable log. Turns out, I had to manually convert them to their primitive types, because even though by syntax it was all nice and well, the compiler didn't like that std::format didn't know how to handle those custom types.
I hope this isn't coming off crude, but I am wondering, why would you change from fmt lib to std::format if you already had it in your code? Was it to create one less dependency? I feel as if I wouldn't have bothered touching that, but also because it creates less a hard reliance on upping the C++ version support, unless that was intentional too.
Did you get compiler errors? If so, you're golden. Cherno describes a situation where the code compiles fine but during runtime it fails. That's way more difficult to spot and debug.
Word of advice to anyone looking at implementing conversion operators for their own types: be *extremely* conservative with them. Mark every one as `explicit` and even then *deeply* question if just using a named function would be better. There are some *extremely rare* cases where a conversion operator makes sense, like a container of `int`s being castable to that same container of `float`s, but in the vast majority of cases having implicit (or even explicit) `bool` conversions from complex types (especially if that conversion is non-trivial) because you think `if (my_thing) {}` just looks cleaner than `if (!my_thing.empty()) {}` will only lead to confusion and headache in the long-run. It's incredibly frustrating that the STL's smart pointers only implement `operator bool` rather than an `bool empty()` member function because it's just another thing you need to memorize the behavior for and potentially without any clear terms to search for.
As for things like containers and other rather complex types I do agree this is a very good advice. I have seen elsewhere people pointing out that the method name "empty()" perhaps was a very poor choice since it is ambiguous. Does it empty the container? Who knows... (Ok, we know the method is actually "clear()" which isn't all that much clearer either.) It should probably have been called something like "isempty()" instead. Anyway, as for the _pointers_ and things that behave like pointers (e.g. smart pointers) I don't think enforcing things like .empty() makes it any clearer at all. (What is an 'empty' pointer btw?) I claim that a pointer type _is_ a boolean type in and of itself; it has the trait that it either "points" (to something) or it doesn't. That's a pointer's two possible states -- boolean in nature. What it points _to_ is not necessarily boolean, but the pointer itself is. Therefore writing things like "if (ptr != NULL)" or "if (ptr == nullptr)" becomes overstating the obvious, just like if you have a "bool is_ready;" and then write "if (is_ready == true)". They all serve nothing but adding verbosity, potentially burying the more significant words. Writing "if (ptr)" or "if (!is_ready)" is both shorter and clearer, as well as following an old and well known idiom in both C and C++. Please don't write Java or Fortran in C++ source files! I somehow expect to see someone writing "if ((is_ready == yes) == true)" any time soon now 'just to be extra clear and unambiguous of the intension' :-)
@@benhetland576 I disagree, but I also know this is a matter of opinion. I think the problem with implicit conversions is that they can happen without your knowledge and can incur an unseen cost. "Smart pointers" *are not* pointers, they are non-trivial class objects; in me experience, less established treating them like pointers has led to a ton of misunderstanding about how they work and edge-cases in their usage (eg: for a pointer, `&*p` does nothing and may even be removed by the compiler, but for a smart pointer you end up with a completely different type). I agree `is_empty` would be a much better name than `empty`, but since it's basically never going to change I stick with `empty` when talking about hypotheticals as well. std::optional uses `has_value` which might be better for smart pointers. My point is that in order to even know how the implicit boolean conversion will work you need to know the type of the object being converted which sometimes isn't possible (templates, `auto`).
@@BryceDixonDev Yes, 'auto' can be a nasty beast sometimes. I think most of its motivation was actually to save some typing with the insanely long templated type names, which again may be regarded as a somewhat unfortunate development in C++. So it is mostly a convenience for "lazy" typists, but it does require familiarity with the type inference rules. As for the "&*p" syntax I don't believe a C++ compiler is at liberty to eliminate it (I admit I didn't actually check what the standard says). This is because both operators may be overloaded even for non-class types, so either or both may return something completely non-intuitive. (&*p may also be different from *&p) This is in part what the smart pointers exploit. The two operators may not be the inverse of each other, and this must be at least taken into account by compilers as well as programmers. In C the matter is different, and there it might be permitted to eliminate them without breaking semantics.
@@benhetland576 I kind of agree with your assessment. Most of us through use know what container.empty() is, yet I one would think that container.is_empty() would be much more clear. One would think that if a type to be returned is a bool where it's typically a question that can be answered with either yes or a no, would have the qualifying transitive verb of "is" to indicate that it's a question of state rather than just "empty" as in a verbal action of something to be done.
Pretty much 99% of conversions operators should be marked explicit (even boolean ones) to prevent weird bugs and unexpected conversions from happening and here is not a single word about it, kinda disappointing. The only exceptions are objects intended to emulate/behave like completely different type, such as std::vector::reference proxy.
I just wanted to write the same... 👍 I actually do like conversion operators, but they can create many problems. So imo it's better to use them sparingly and when with explicit. Still, in some places they can enable things that weren't possible otherwise.
@bsdooby No, not necessarily. Take Color as an example. You might want to be able to convert it into 4 floats (vec4f). In this case you want to have the option to do so, but should really require that operation to be explicit to prevent weird bugs and make the code more readable. You can see that it's converting to vec4. Also the real issue comes form having multiple conversion operators. Say if the vec4 also had one that implicitly converts to 'something else'. Now passing the color could first convert to vec4 implicitly and then from vec4 to 'something else', all in the same place
Was just going to mention this myself. Like with single argument constructors, the default should be to add the explicit keyword and only omit if absolutely necessary
re: what to cover next in the C++ series I'm not sure if you have yet covered these? - constexpr - async/await - C++ 20 modules. - C++20 "concepts" (template parameter (?) constraints)
Nice Video! As for the next c++ series episode: Maybe explain how to make things threadsafe, for example your Ref class, which needs to be thread safe. Would be super interesting
always very usefull. I don't use conversion operator. I have a MyString class for example but I neither use the operator char* because sometimes I have to cast it anyway (as you shown). so I prefer to be forced to use methods and fields. It's safer and more readable (and I mantain a TON of code). Good to know C++ series is back. I learned a LOT from it!!!
13:26 I feel like if you are going to be casting to a float then maybe its not really a float you are after in this case. You are after milli / secs or some sort of time. So maybe adding a typedef (Which ultimately ends up being a float) for that would make it clearer. You are right that the implicit casting is dangerous, but I really like it as a feature and want to make it useful somehow.
Actually after thinking about it, casting to the type of data in that formatter is the "right" way of doing this imo, because. Although ImGui::Text() is defined elsewhere, its such an amorphous definition that its only really implemented at the point that it is called, which means the point that it is called should make the way that it is being called explicit. Since in this case the formatter is formatting as though the float supplied represents millis, then the variable for that format should be cast since that style of writing mimics normal function declaration. eg text(Format, millis, int) mimics text(String format, float millis, int samples)
I just saw an interview of Bjarne Stroustrup in which he said that implicit conversions were not his idea and they were a mistake that he tried to get rid of at one point, but the C++ powers that were at the time wouldn't have it and soon thereafter it became too late.
Would be good to also talk about explicit vs implicit conversion operators. If you mark your operator as explicit: class my_class{ public: explicit operator bool() const{ return true; } }; you can cast to a bool like this: bool my_value = static_cast(my_class{}); but not like this: bool my_value = my_class{}; which you can without marking it as explicit. So you can't do it by accident.
Man, I know this kind of topic, but I felt happy when you uploaded this vid Cuz it is not about your personal engine's emotion stories as u regularly do.
A way of making it more obvious without making it (much) more verbose is using template parameters. std::milli, std::micro etc. already exist, and you can just give the class a template parameter that sets what standard conversion does if you really want conversion, and you can have just one templated get function which then based on the ratio in in the template parameter gives you the format you actually want without any runtime slowdowns etc.
I think my favorite operator is the UDL suffix operator. I've been working on a measurement library and implementing all kinds of operations to allow quick and easy conversions. For instance, I can do distance d = 1ft; then d /= 12; and print it out and get 1in or I can have d = 1ft; d += 1in; and printing it out yields 13in. It's a lot of fun.
if you're using literally (hehe) literals such as "ft" that's actually an ill-formed program as user-defined literals have to start with an '_' (so: 2_ft e.g.) because those without one are reserved for future standard library use. While compilers might allow you to do it (and maybe just print a warning) you should think about changing it and conforming with the standard.
@@vaijns No thanks. I'm just using it as a testbed for ideas I'm putting into my own language, and my language doesn't force such things into the global namespace by default. For my language you'll have to import a particular module to use them and further place that module into the global namespace to make use of them as just 1ft or 2in and so on. My language also doesn't have rules about polluting namespaces because it's up to you the user to place things where you want to use them. I've always hated the idiomatic method in C++ of only using standard things through std:: and with a proper module system I would hope that becomes a thing of the past, but I haven't read what they're doing with modules yet and I'm nearly done with my own language anyway.
The key thing about conversion operators is not to use them )) maybe in rare cases and exclusively in a string, for easier output to the log. For everything else, the explicit function toNeedType() is always better because it is explicit, and explicit is always better than implicit.
19:20 I wouldn't say it keeps everything clean. I'd rather say having implicit conversion is more convenient (to write, or more precisely not to write code, but not necessarily to read, understand). It's a clever hack to make new behavior compatible with existing code. But implicit conversion makes it harder to understand the code (for everyone, but mainly new developers). It requires the reader to be familiar with the internals and have the implicit conversion on their mind, cache, increasing cognitive load. It's not strictly about code readability, it's faster to read less words, code. But to understand what is happening does not linearly correlate with word count. ;)
Just wondering why you would not synchronize access to bool AsyncAssetResult::IsReady? I suppose the flag would be set from another thread on load completion, wouldnt it? Correct me if im wrong, but it is not safe to do that and volatile/atomic should be rather used?
I'd like to know how you detect those memory error that just cause a crash each now and then and often in a different part of the code bacause another piece of code messed up the memory. Threads and threadpool also. thanks a lot.
@TheCherno You should really use 'explicit' on boolean conversion operators. That bit me in the but many times in my project. You would still be able to do 'if (entity)' without the explicit cast, but it won't allow something like 'MyFunction(entity)' where MyFunction is defined as 'MyFunction(int x)'.
Templates :). I read an article about how use templates in classes in different files (.h and .cpp). The article said that you include at the end of the .h file the .cpp file. I mean literally including it: #include "class.cpp". Also said " it's magic, don't ask why. " That could be a nice topic to talk about, not only in the vsc++ but also in gcc/g++ on Linux . Stuffs like instancing an specific type of a class method (only for in the actual implementation do something with that type) is not allowed directly (as far as I know), although can be emulated.
Imho the bug is more of a problem of printf like interface rather than conversion operator (it can also happen with custom structs, or even std::string), with std::format it wouldn't happen. And then regarding implicit conversions, ideally just use auto, that always prevents implicit conversions and is still very readable. The first example of seconds vs miliseconds is probably the most convincing anti-case, however do you really use miliseconds as a double in a game engine? :P
Personally I am usually okay with bool conversion operators, but I very rarely go beyond that unless I'm making a type with the express intent of it behaving as another type just with added details behind the scenes, such as the smart reference that you use in hazel where you likely include a conversion operator to the referenced type.
I know everyone has been stating this but, always make a conversion operator explicit because if we were to implicitly convert something the compiler might convert our type to another type we don't want which leads to unintended behavior.
Conversion Operators are surprising side effects in a lot of cases, which is why they weren't included in F# and most ML languages, you have to explicitly call a function to convert one thing to another. The same perils happens on C#, ironically with conversion operators.
The issue with the bug to me sounds like it's more of a bug with c style variatic arguments. If they used variatic template arguments, the type is retained and therefore the context of the type. This would never have happened with fmt lib for instance.
you'd think imgui would parse the string and then cast all parameters just to be sure and throw the necessary errors I mean even inputs from scanf are casted to their types.
Conversion is also only 1 level deep. If you have a conversion from A to B and B to C, you can't convert from A to C directly. (Which is a good thing imo)
I thought pointers were automatically deleted when they go out of scope, if they were defined in that scope, and not moved or assigned anywhere... is this not fact?
I think the problem is that you can seemingly use a variable while in actually an implicit conversion and maybe a nontrivial one is executed. That is bad code practice. I can see their use in an integer container type and implicit float conversion. So I dont like them too much. I like explicit code, because then it is very clear what is happening and when something wrong is happing.
Complaining about vararg don't assuming that structure conversion operators, lol Jokes aside, I dunno, I've seen some people (even my lead grade co-worker) that thought varargs behave like typed arguments just because they mentioned the type in the string in first argument, causing UB's Never trust varargs! Always explicitly cast to what type you assuming when passing as an argument (like this `printf("%f ", (float)variable)`), otherwise it's always UB. For example in "Microsoft x64" calling conventions if your struct is larger than 8 bytes it will be passed as pointer, otherwise it passed as value (I don't remember how it was on unix-like systems)
@@benhetland576 As we seen in this video, people can put there an *object* thinking their *type conversion operator* will handle this, or from my experience something like *std::optional* to *%f* or *std::pair/std::tuple* into *%d,* that causes UB (first of all: it's not guaranteed by calling conventions where and how that argument and the rest arguments are placed, second: it's not guaranteed by standard how compilers implement those classes)
I find the culture of programming in each language very interesting. Personally, as a programmer that started in Java, I loved being crystal clear about what each method and variable did with my names. But... in C++, it often feels like that isn't really the highest priority in general. Intuitiveness and clarity is much less emphasized and things like... "less code" is prized more. Perhaps it is because C++ means people might come from even lower, and they are used to programming for performance and space saving. So naming and intuitiveness is a luxury, and y rite in cumpleet wrds wen u kn save a few kb her n ther.
As someone who also started with java has their first language I can agree with you completely, I also think the "less code is prized" idea comes from the idea that the more code you write the more likely you are to shoot yourself in the foot (more likely to mess up).
@@SilentFire08 In general I think it's much the opposite, the more explicit you are the less likely you are to shoot yourself in the foot ... especially with these implicit conversions
Nowadays, it's a matter of preference and how people learned how to code in C++ imo. People can explicitly choose to do more implicit stuff, some don't. I, personally, from the very beginning, always preferred explicit writing and make my code as convenient and easy to understand so there is less guessing to do. Ofc some things are no brainers there because there are a bunch of common practices that are done in C++ that may throw some people off, those people probably never studied the language like from the very basics of it but instead just decided to dive into it without any preparation, or something like that. Common practices exist everywhere. And at my work where we do C++ the "less code" is not prized, and I also never prized this idea, it's very stupid, and C++ is my first language as well and it's great. (and easy hehe)
I write C and Python code at work. I really like Python in that I can be lazy as possible and have the language do everything for me. So typing less. Less words. Less code. Keep things simple and let the computers do all the work.
Operator overloading is AWESOME feature of C++. I had problem on my embedded system with big array of "past" values for my logging that just grew a lot for each parameter. I solved it by drop-in-replacement of Half library which basically implements float as 16 bit with less precision. It was so awesome to just be able to do simple typedef and swap normal float like that.. Just insane.
I use them with care. Sometimes they're nasty. In those cases I add a couple of to_underlying() functions to the struct to do the explicit conversation for me.
Nothing wrong with conversion operators, but as a general rule I always make them explicit. The same applies to 1 argument ctors. I don't want implicit conversions happening without me knowing about them.
Might just be very opinionated of me to say this, but from my experience as a software developer as soon as your team size is larger than 1, choosing to be less explicit to save like 5-10 characters is 100% a mistake. Especially since 9 times out of 10 it will be semantically incorrect afterwards unless the class you are converting from is nothing but a wrapper (like the std pointers). The timer example you showed perfectly covers everything that's dangerous with this. A timer is a timer. It's not a time value, by its very definition of how we understand that word in English.
What should I cover next in the C++ series? Let me know below 👇
You can try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/TheCherno . You’ll also get 20% off an annual premium subscription.
variadic templates would be just perfect theme for next video!!! like how to make tuple with them etc
Std::move and std:: forward next please 🙂
@@AndreiSokolov-k7jare you following the same game dev course as me? Thats exactly where im stuck lol
Do you mind explain the code
"""
for(const auto& [name, perFrameData] : prevFrameData)
"""
@13:08
Hi, I agree with the "optional unwrap" being a conversion, but the is ready seems like ... well ... it doesn't seem like a conversion but more like a query. Basically you use the same language to describe to semantically different operations. await vs query. What would happen if he thing you are awaiting (the mesh here) would be convertible to bool as well. Should the compiler pick the longer conversion chain or not? If it's not ambiguous it is something you would never remember as an avarage c++ programmer. (At work we use !!some_var to force the bool conversion, I don't like that either tho 😀)
THE C++ SERIES IS BACK
Multithreading , threadpool ,All threading concept for interview preparation will be damn helpful
Next topic: the missing promised template videos.
lmao I just got to that section in the course!
the missing "definitely going to happen" opengl videos too...
I use conversion operators a lot for my custom math library. Being to cast a 2d vector of ints into a 3d vector of floats is pretty nice.
I've used conversion operators for something similar to this. Libraries I'm currently using are Raylib, ReactPhysics3D, and Recast Navigation.
All three have their own versions of Vector2/3, Matrix and Quaternion. Most of the time they are the exact same, a Vector3 is usually just three floats in a struct for example... but having my own maths library that has conversion operators to all three libraries has helped massively.
Before using this approach, I was having to take an RP3D Vector3 for the physics object position and convert it to a Raylib Vector3D to update the object's position (for rendering) and to RecastNav Vector3 for navigation.
I literally spent a day on debugging this at work two days ago. I was doing a rewrite of our logging system, because the old one sufferred from deadlocks and memory errors. In a few instances, there were logs which logged 8 different variables. Most of the variables were custom types, even though they really represented primitive types, such as int8_t, int16_t, etc. And those custom types also had overloaded conversion operators to those original types. They were formatted via fmt::format, but since we are already using C++20, we agreed to migrate all fmt instances to std::format. In the editor, the code behaved like all was well, but when I tried to compile it, I got several pages worth of an error log per call of that 8-variable log. Turns out, I had to manually convert them to their primitive types, because even though by syntax it was all nice and well, the compiler didn't like that std::format didn't know how to handle those custom types.
I hope this isn't coming off crude, but I am wondering, why would you change from fmt lib to std::format if you already had it in your code? Was it to create one less dependency? I feel as if I wouldn't have bothered touching that, but also because it creates less a hard reliance on upping the C++ version support, unless that was intentional too.
Did you get compiler errors? If so, you're golden. Cherno describes a situation where the code compiles fine but during runtime it fails. That's way more difficult to spot and debug.
@@sledgex9 Yeah, I pray that is not my case, haha
Word of advice to anyone looking at implementing conversion operators for their own types: be *extremely* conservative with them. Mark every one as `explicit` and even then *deeply* question if just using a named function would be better.
There are some *extremely rare* cases where a conversion operator makes sense, like a container of `int`s being castable to that same container of `float`s, but in the vast majority of cases having implicit (or even explicit) `bool` conversions from complex types (especially if that conversion is non-trivial) because you think `if (my_thing) {}` just looks cleaner than `if (!my_thing.empty()) {}` will only lead to confusion and headache in the long-run.
It's incredibly frustrating that the STL's smart pointers only implement `operator bool` rather than an `bool empty()` member function because it's just another thing you need to memorize the behavior for and potentially without any clear terms to search for.
As for things like containers and other rather complex types I do agree this is a very good advice. I have seen elsewhere people pointing out that the method name "empty()" perhaps was a very poor choice since it is ambiguous. Does it empty the container? Who knows... (Ok, we know the method is actually "clear()" which isn't all that much clearer either.) It should probably have been called something like "isempty()" instead.
Anyway, as for the _pointers_ and things that behave like pointers (e.g. smart pointers) I don't think enforcing things like .empty() makes it any clearer at all. (What is an 'empty' pointer btw?) I claim that a pointer type _is_ a boolean type in and of itself; it has the trait that it either "points" (to something) or it doesn't. That's a pointer's two possible states -- boolean in nature. What it points _to_ is not necessarily boolean, but the pointer itself is. Therefore writing things like "if (ptr != NULL)" or "if (ptr == nullptr)" becomes overstating the obvious, just like if you have a "bool is_ready;" and then write "if (is_ready == true)". They all serve nothing but adding verbosity, potentially burying the more significant words. Writing "if (ptr)" or "if (!is_ready)" is both shorter and clearer, as well as following an old and well known idiom in both C and C++. Please don't write Java or Fortran in C++ source files! I somehow expect to see someone writing "if ((is_ready == yes) == true)" any time soon now 'just to be extra clear and unambiguous of the intension' :-)
@@benhetland576 I disagree, but I also know this is a matter of opinion. I think the problem with implicit conversions is that they can happen without your knowledge and can incur an unseen cost. "Smart pointers" *are not* pointers, they are non-trivial class objects; in me experience, less established treating them like pointers has led to a ton of misunderstanding about how they work and edge-cases in their usage (eg: for a pointer, `&*p` does nothing and may even be removed by the compiler, but for a smart pointer you end up with a completely different type).
I agree `is_empty` would be a much better name than `empty`, but since it's basically never going to change I stick with `empty` when talking about hypotheticals as well. std::optional uses `has_value` which might be better for smart pointers.
My point is that in order to even know how the implicit boolean conversion will work you need to know the type of the object being converted which sometimes isn't possible (templates, `auto`).
@@BryceDixonDev Yes, 'auto' can be a nasty beast sometimes. I think most of its motivation was actually to save some typing with the insanely long templated type names, which again may be regarded as a somewhat unfortunate development in C++. So it is mostly a convenience for "lazy" typists, but it does require familiarity with the type inference rules. As for the "&*p" syntax I don't believe a C++ compiler is at liberty to eliminate it (I admit I didn't actually check what the standard says). This is because both operators may be overloaded even for non-class types, so either or both may return something completely non-intuitive. (&*p may also be different from *&p) This is in part what the smart pointers exploit. The two operators may not be the inverse of each other, and this must be at least taken into account by compilers as well as programmers. In C the matter is different, and there it might be permitted to eliminate them without breaking semantics.
@@benhetland576 I kind of agree with your assessment. Most of us through use know what container.empty() is, yet I one would think that container.is_empty() would be much more clear. One would think that if a type to be returned is a bool where it's typically a question that can be answered with either yes or a no, would have the qualifying transitive verb of "is" to indicate that it's a question of state rather than just "empty" as in a verbal action of something to be done.
Great tutorial-explanation, simple example, real-world example, important issues. (also really like the new mic)
I appreciate these thorough, efficient, fair minded, and educational explorations of specific topics.
Pretty much 99% of conversions operators should be marked explicit (even boolean ones) to prevent weird bugs and unexpected conversions from happening and here is not a single word about it, kinda disappointing.
The only exceptions are objects intended to emulate/behave like completely different type, such as std::vector::reference proxy.
I just wanted to write the same... 👍 I actually do like conversion operators, but they can create many problems. So imo it's better to use them sparingly and when with explicit. Still, in some places they can enable things that weren't possible otherwise.
@@epiphaeny How would explicit help in that case? Conversion ops are there to not have explicits, aren't they?
@bsdooby No, not necessarily. Take Color as an example. You might want to be able to convert it into 4 floats (vec4f). In this case you want to have the option to do so, but should really require that operation to be explicit to prevent weird bugs and make the code more readable. You can see that it's converting to vec4.
Also the real issue comes form having multiple conversion operators. Say if the vec4 also had one that implicitly converts to 'something else'. Now passing the color could first convert to vec4 implicitly and then from vec4 to 'something else', all in the same place
Was just going to mention this myself. Like with single argument constructors, the default should be to add the explicit keyword and only omit if absolutely necessary
@@duckdoom5 It's not obvious which color format you went to use rgb hsl...
Finally my man is back please keep these coming
I really like the jetbrains IDEs for this, because they also show a symbol, if something is implicitly converted with an operator.
re: what to cover next in the C++ series
I'm not sure if you have yet covered these?
- constexpr
- async/await
- C++ 20 modules.
- C++20 "concepts" (template parameter (?) constraints)
New cherno video, life's good.
Nice Video!
As for the next c++ series episode: Maybe explain how to make things threadsafe, for example your Ref class, which needs to be thread safe. Would be super interesting
always very usefull. I don't use conversion operator. I have a MyString class for example but I neither use the operator char* because sometimes I have to cast it anyway (as you shown). so I prefer to be forced to use methods and fields. It's safer and more readable (and I mantain a TON of code). Good to know C++ series is back. I learned a LOT from it!!!
13:26
I feel like if you are going to be casting to a float then maybe its not really a float you are after in this case. You are after milli / secs or some sort of time.
So maybe adding a typedef (Which ultimately ends up being a float) for that would make it clearer.
You are right that the implicit casting is dangerous, but I really like it as a feature and want to make it useful somehow.
Actually after thinking about it, casting to the type of data in that formatter is the "right" way of doing this imo, because.
Although ImGui::Text() is defined elsewhere, its such an amorphous definition that its only really implemented at the point that it is called, which means the point that it is called should make the way that it is being called explicit.
Since in this case the formatter is formatting as though the float supplied represents millis, then the variable for that format should be cast since that style of writing mimics normal function declaration.
eg text(Format, millis, int)
mimics
text(String format, float millis, int samples)
I used this years ago to make converting my own rect/point/etc objects to win32 api struct equivalents. Worked fine.
I just saw an interview of Bjarne Stroustrup in which he said that implicit conversions were not his idea and they were a mistake that he tried to get rid of at one point, but the C++ powers that were at the time wouldn't have it and soon thereafter it became too late.
Would be good to also talk about explicit vs implicit conversion operators.
If you mark your operator as explicit:
class my_class{
public:
explicit operator bool() const{ return true; }
};
you can cast to a bool like this:
bool my_value = static_cast(my_class{});
but not like this:
bool my_value = my_class{};
which you can without marking it as explicit. So you can't do it by accident.
at that point, why not just use a method call lol
nice one! didn't know that yet, but i'm already a big fan of operator overloading and this seems similiar
I'm learning C++ now thanks to your videos, thankyou!
In my opinion all conversation operators should be explicit and using format strings should be avoided whenever possible.
Man, I know this kind of topic, but I felt happy when you uploaded this vid
Cuz it is not about your personal engine's emotion stories as u regularly do.
A way of making it more obvious without making it (much) more verbose is using template parameters. std::milli, std::micro etc. already exist, and you can just give the class a template parameter that sets what standard conversion does if you really want conversion, and you can have just one templated get function which then based on the ratio in in the template parameter gives you the format you actually want without any runtime slowdowns etc.
I think my favorite operator is the UDL suffix operator. I've been working on a measurement library and implementing all kinds of operations to allow quick and easy conversions. For instance, I can do distance d = 1ft; then d /= 12; and print it out and get 1in or I can have d = 1ft; d += 1in; and printing it out yields 13in. It's a lot of fun.
if you're using literally (hehe) literals such as "ft" that's actually an ill-formed program as user-defined literals have to start with an '_' (so: 2_ft e.g.) because those without one are reserved for future standard library use.
While compilers might allow you to do it (and maybe just print a warning) you should think about changing it and conforming with the standard.
@@vaijns No thanks. I'm just using it as a testbed for ideas I'm putting into my own language, and my language doesn't force such things into the global namespace by default. For my language you'll have to import a particular module to use them and further place that module into the global namespace to make use of them as just 1ft or 2in and so on. My language also doesn't have rules about polluting namespaces because it's up to you the user to place things where you want to use them. I've always hated the idiomatic method in C++ of only using standard things through std:: and with a proper module system I would hope that becomes a thing of the past, but I haven't read what they're doing with modules yet and I'm nearly done with my own language anyway.
Let's goooo were back
The key thing about conversion operators is not to use them )) maybe in rare cases and exclusively in a string, for easier output to the log. For everything else, the explicit function toNeedType() is always better because it is explicit, and explicit is always better than implicit.
19:20 I wouldn't say it keeps everything clean.
I'd rather say having implicit conversion is more convenient (to write, or more precisely not to write code, but not necessarily to read, understand).
It's a clever hack to make new behavior compatible with existing code.
But implicit conversion makes it harder to understand the code (for everyone, but mainly new developers). It requires the reader to be familiar with the internals and have the implicit conversion on their mind, cache, increasing cognitive load.
It's not strictly about code readability, it's faster to read less words, code. But to understand what is happening does not linearly correlate with word count. ;)
As a Chinese high school student, I like your C course very much. Thank you teacher. It would be perfect if you make a collection video.
finally! that long awaited topic!
Finally! A new video after a long time😄
What visual studio theme do you use?
Its the visual assist extension, I'm pretty sure its paid though.
You can change the class color and variables colors and so on manually and make it your own theme.
i love this c++ series
Thank you so much for making this video 👍👍👍
Woah, I had to check the date to see if this wasn't an older one I had missed.
Have you talked about object slicing yet?
Just wondering why you would not synchronize access to bool AsyncAssetResult::IsReady? I suppose the flag would be set from another thread on load completion, wouldnt it? Correct me if im wrong, but it is not safe to do that and volatile/atomic should be rather used?
After C++11 there are also the std::promise and std::future templates to do this kind of stuff.
Could you please explain what do you mean by talking about the c++ learning curve, at the end of the video?
I'd like to know how you detect those memory error that just cause a crash each now and then and often in a different part of the code bacause another piece of code messed up the memory.
Threads and threadpool also. thanks a lot.
@TheCherno You should really use 'explicit' on boolean conversion operators. That bit me in the but many times in my project. You would still be able to do 'if (entity)' without the explicit cast, but it won't allow something like 'MyFunction(entity)' where MyFunction is defined as 'MyFunction(int x)'.
Templates :).
I read an article about how use templates in classes in different files (.h and .cpp). The article said that you include at the end of the .h file the .cpp file. I mean literally including it: #include "class.cpp". Also said " it's magic, don't ask why. "
That could be a nice topic to talk about, not only in the vsc++ but also in gcc/g++ on Linux . Stuffs like instancing an specific type of a class method (only for in the actual implementation do something with that type) is not allowed directly (as far as I know), although can be emulated.
Imho the bug is more of a problem of printf like interface rather than conversion operator (it can also happen with custom structs, or even std::string), with std::format it wouldn't happen. And then regarding implicit conversions, ideally just use auto, that always prevents implicit conversions and is still very readable. The first example of seconds vs miliseconds is probably the most convincing anti-case, however do you really use miliseconds as a double in a game engine? :P
Thanks cherno you really helped our community.❤
@The_Cherno what visual studio theme are you using in the tutorial?
Personally I am usually okay with bool conversion operators, but I very rarely go beyond that unless I'm making a type with the express intent of it behaving as another type just with added details behind the scenes, such as the smart reference that you use in hazel where you likely include a conversion operator to the referenced type.
How about socket programming in c++ or interoperability with other languages?
I would have kept the ref conversion but not the bool. I would view a bool conversion in this context more as a validity check than a readiness check.
0:23
totally not me 👀
To cover next: some context about C++ releases and what's new in C++23. What should we already be using from C++20?
Yay itz back but sir please start a vulkan series. Please🙏🙏🙏🙏
I know everyone has been stating this but, always make a conversion operator explicit because if we were to implicitly convert something the compiler might convert our type to another type we don't want which leads to unintended behavior.
Conversion Operators are surprising side effects in a lot of cases, which is why they weren't included in F# and most ML languages, you have to explicitly call a function to convert one thing to another.
The same perils happens on C#, ironically with conversion operators.
The issue with the bug to me sounds like it's more of a bug with c style variatic arguments. If they used variatic template arguments, the type is retained and therefore the context of the type.
This would never have happened with fmt lib for instance.
what theme do you use in visual studio?
Implicit casts / overloaded conversions are the first step to hell
you'd think imgui would parse the string and then cast all parameters just to be sure and throw the necessary errors I mean even inputs from scanf are casted to their types.
How does the async load asset function work? I don‘t see where it is blocking until loaded before isReady is true…
damn real thanks I never knew about this operator!
Anyone knows what's the font family cherno is using? Thanks
I love conversion operator :)
shouldn't you use conversion operators with "explicit" keyword to prevent unwanted conversions?
WE ARE SO BACK RAHHHH
Will there be tutorial for modern c++ and all features of modern c++
I like your microphone!
It's one of the theatrical types, right?
It catches your voice no matter how far away you are!
Conversion is also only 1 level deep.
If you have a conversion from A to B and B to C, you can't convert from A to C directly. (Which is a good thing imo)
please make a video on static_cast< >
16:21 Use can also use "auto" ...
I thought pointers were automatically deleted when they go out of scope, if they were defined in that scope, and not moved or assigned anywhere... is this not fact?
Thank you!
What are your feelings about c-style initializers vs "modern" style? E.g int a = 2; vs int a(2);
Which font style is this?
this series needs to come back lol
I think the problem is that you can seemingly use a variable while in actually an implicit conversion and maybe a nontrivial one is executed. That is bad code practice. I can see their use in an integer container type and implicit float conversion. So I dont like them too much. I like explicit code, because then it is very clear what is happening and when something wrong is happing.
We are so fucking back
Complaining about vararg don't assuming that structure conversion operators, lol
Jokes aside, I dunno, I've seen some people (even my lead grade co-worker) that thought varargs behave like typed arguments just because they mentioned the type in the string in first argument, causing UB's
Never trust varargs! Always explicitly cast to what type you assuming when passing as an argument (like this `printf("%f
", (float)variable)`), otherwise it's always UB. For example in "Microsoft x64" calling conventions if your struct is larger than 8 bytes it will be passed as pointer, otherwise it passed as value (I don't remember how it was on unix-like systems)
Well, the %f expects a double, but a float argument implicitly gets promoted to a double anyway when you call a variadic function like printf.
@@benhetland576 As we seen in this video, people can put there an *object* thinking their *type conversion operator* will handle this, or from my experience something like *std::optional* to *%f* or *std::pair/std::tuple* into *%d,* that causes UB (first of all: it's not guaranteed by calling conventions where and how that argument and the rest arguments are placed, second: it's not guaranteed by standard how compilers implement those classes)
I find the culture of programming in each language very interesting.
Personally, as a programmer that started in Java, I loved being crystal clear about what each method and variable did with my names.
But... in C++, it often feels like that isn't really the highest priority in general.
Intuitiveness and clarity is much less emphasized and things like... "less code" is prized more.
Perhaps it is because C++ means people might come from even lower, and they are used to programming for performance and space saving. So naming and intuitiveness is a luxury, and y rite in cumpleet wrds wen u kn save a few kb her n ther.
Very true! I'm also one who started with Java, but I see things similarly.
As someone who also started with java has their first language I can agree with you completely, I also think the "less code is prized" idea comes from the idea that the more code you write the more likely you are to shoot yourself in the foot (more likely to mess up).
@@SilentFire08 In general I think it's much the opposite, the more explicit you are the less likely you are to shoot yourself in the foot ... especially with these implicit conversions
Nowadays, it's a matter of preference and how people learned how to code in C++ imo.
People can explicitly choose to do more implicit stuff, some don't.
I, personally, from the very beginning, always preferred explicit writing and make my code as convenient and easy to understand so there is less guessing to do.
Ofc some things are no brainers there because there are a bunch of common practices that are done in C++ that may throw some people off, those people probably never studied the language like from the very basics of it but instead just decided to dive into it without any preparation, or something like that.
Common practices exist everywhere.
And at my work where we do C++ the "less code" is not prized, and I also never prized this idea, it's very stupid, and C++ is my first language as well and it's great. (and easy hehe)
I write C and Python code at work. I really like Python in that I can be lazy as possible and have the language do everything for me.
So typing less. Less words. Less code. Keep things simple and let the computers do all the work.
your timer is multiplying by a float and returning a double? you might want to use 0.01 without the f to ensure it's a double literal
Thanks
As a C/Rust programmer, I really don't like operator overloading, but it was a good video as always
Operator overloading is fine imo as long as there is no ambiguity with whats happening
Utilizo tus videos para aprender ingles y c++
yay a new videooo
Please make a video on vptr and vtable
Finally!!!!!
Operator overloading is AWESOME feature of C++.
I had problem on my embedded system with big array of "past" values for my logging that just grew a lot for each parameter.
I solved it by drop-in-replacement of Half library which basically implements float as 16 bit with less precision.
It was so awesome to just be able to do simple typedef and swap normal float like that.. Just insane.
I use them with care. Sometimes they're nasty. In those cases I add a couple of to_underlying() functions to the struct to do the explicit conversation for me.
Well now you're just comparing floats and Oranges
it's happening i can't believe it
Got a job because of your courses,
Thank you.
That sounds cool, but it introduces a whole new world of potential bugs
I love your C++
This series is down to 1 episode per Visual Studio release version, huh?
0:01 "Ah, this is going to be controversial" you meant "Ah, this is going to be conversional", right 😁😁
y does msvc complain about me casting a float into an int c-style
aka
float a = 5.4f;
std::cout
Nothing wrong with conversion operators, but as a general rule I always make them explicit. The same applies to 1 argument ctors. I don't want implicit conversions happening without me knowing about them.
Might just be very opinionated of me to say this, but from my experience as a software developer as soon as your team size is larger than 1, choosing to be less explicit to save like 5-10 characters is 100% a mistake. Especially since 9 times out of 10 it will be semantically incorrect afterwards unless the class you are converting from is nothing but a wrapper (like the std pointers).
The timer example you showed perfectly covers everything that's dangerous with this. A timer is a timer. It's not a time value, by its very definition of how we understand that word in English.
Wait until you delve in the insanity of templated conversion operators
Where's the type safety :(
LETSGO🎉
fking overloaaaad !
直接看最后一集对比之前的视频,声音变化真大