I've been out of the loop in C++ development for a few years already and I'm just learning that concepts finally made it into C++20. That, and there's GCC 11. Shit's bananas.
@@LesleyLai Msvc team plan to make a big update of their toolset to solve many known problems but require breaking changes. So this may happen in the future, but I don't know when. My guess is that it might happen when the STL will get modularized.
This is not "multiple destructors". An Optional only has one destructor (a trivial destructor) and an Optional only has one destructor (a non trivial destructor)! However there are multiple destructors in C++. Try to look at the generated assembler for a classes destructor for a class with non-trivial destructible members.
In any case you end up with user-declared destructor. In such case compiler does not generate implicit move constructor for you. Therefore, you have to define move constructor explicitly, otherwise your optional is not movable. So, is there a way to not define `~Struct() = default` at all?
Regarding the audio clicking sound. Have you tried increasing the buffer size in whatever software you use to record? If the buffer is too small, then every once in a while the computer won't be able to process a buffer completely in time and this results in random clicks in the recording. I've definitely been there. Super annoying, super random
Am on premium, mobile, saw two black screens. I am behind a PiHole though, but premium shouldn't even try to load ads. (Never seen this behaviour before)
Now I must have chosen the wrong Jason turner weekly. This one flew right over my head. I can't still wrap my head around its necessity. But, well, thanks anyway.
Yeah. The purpose of the union is to circumvent the default construction of the value and the unconditional invocation of the value destructor since we don't know whether an optional contains a value at compile time. Also, an optional can contain types without a default constructor.
I don't get, how you access the destructor of data in the struct optional_nontrivial. (9:45 at line 8) The union with the data member is defined in optional, and optional inherits from optional_nontrivial not the other way round.
The static_cast casts the this ptr to the derived type that's injected via template param, that's why data is accessible. This technique is called static polymorphism. Neat little trick.
Apparently, code on 9:40 can't be compiled with Optional being instantiated. Because of the union for non-trivial destructible type and because of line 8 where unknown name Contained being used. But that is not my question, my question is - in the presented shape code does compile, that mean that gcc does not fully figured out how exactly you can destruct the object, but already knew that is is not trivially destructible. That level of laizyness I was not expecting, do you have any other examples or explainations how it works under the hood?
Not naively, SFINAE would not work in this case, because "Contained" is a template parameter of the "Optional" class template and not of a member function (destructor) template. One way to make it work with SFINAE would be to templatize the destructor using a dummy template parameter that default to "Contained": template class Optional { ... template ~Optional() { ... } }
How is Optional_nontrivial able to use static_cast(this)->data.~Contained()? Is that SFINAE? Or is just the case that it doesn't matter at that level of the file because it is still a template? It can only generate an error if during template substitution it fails to compile? It feels like a dependency that breaks encapsulation to me.
3 роки тому
SFINAE works only at the signature. It doesn't matter because it's a template. When used, Contained should be complete.
I like all your c++posts. But one thing I don't like, or say a suggestion, is that I have always to watch them on a big monitor. The font is way too small for my phone, even too small for my 12 inches ipad. I hope all your future post at least good for watching on ipad. Thankd
Doesn't the operator= require a placement new in case the Optional is uninitialized? (If the machinery is there, it should also work for non-trivial classes to really illustrate the point.)
Let's make the C++ language even more complex. Isn't it possible that the C++ compiler itself detects that a C++ destructor only does trivial stuff and can therefore be subject to optimizations?
7:30 The code in line 7 shouldn't work, should it? static_cast(this)->data.~Contained(); The compiler doesn't know the identifier 'Contained' at that moment.
@@MaceUA No this code is not correct, I could put there `~X` and it still not have error as this code it not used any where. Even more, I do not think that template arguments names have any effect on any thing outside given template scope: godbolt.org/z/aarc1M hard error when function i used.
@@WouterStudioHD This should not matter, try doing this is godbolt and see results, overall I come to conclusion that destruction name could be named same as any other function, only in case of template scope C++ can replace template argument name with correct type. Result is that if destructor function name match class then code will compile, other wise you will have error.
Andrey Alexandresku talk about conxtexpr if just came up to my mind. That could easily be written as if constexpr (someting) { ~Optional() {} } Sadly that is not a thing in C++. Hope Metaclasses/codegen will help a little bit.
You are remembering an old version of constexpr if, when it was allowed to be used without introducing a scope. It had fearsome backlash from Stroustrup and few others.
@@dennisrkb These are not two destructors. He used CRTP to inherit from a base class that has one and the only destructor. At compile time, C++ detects whether or not the template parameter is trivially destructible then inherits from the appropriate class, which has the destructor. All this seem like the class has multiple destructors but it doesn't. This is clear, because : template class MyClass {}; is not a class. It generates different classes (signatures) according to the template parameter. So Optional and Optional like shown in the video will generate 2 different class signatures. This is also proven by the fact that you can't treat one as the other in for example a copy constructor. Now to your main question: Imagine: class MyClass { int a; public: Myclass(int _a): a{_a} {} ~MyClass() = default; }; MyClass is not default constructible but it is default destructible.
@@akj7 thx for the elaborate answer. My confusion is around 11:53. Optional is a class with 2 dtors, one defaulted, the other conditionally defined. How does this compile if the condition is true and both definitions are present?
@@dennisrkb Oh i see. Notice the 'constexpr' before the destructors? Notice the 'require'? Just like the explanation above, if the compiler notices that the template parameter is not trivially destructible, the first destructor is enabled (default destructors don't appear in the assembly output, meaning they are ignored), if not only the default constructor is available. All this is set at compile time.
Jason: thank you for another good video, however std::string seems not to be a good example because you can't have a union containing a non-static data member with a non-trivial special member function. Therefore, creating an Optional with string fails. Any comment?
You can have a member with a special member function in a union since c++11. However, the special member function will be deleted on the union if you don't implement it yourself. I don't know how this would work for an anonymous union though, as you need a name to define a destructor.
@@IsaacClancy That's true. However even having a union name, it would be very difficult to implement a destructor for std::string because it would be necessary that you know very intricate details about the std library implementation. I think that Jason can suggest another example. At the end, what he want to show, and for many has been show very well, do not depends on the std library. Anyway, the current example has the fault that if you implement it as it is, it will give you a core dumped, creating confusion for a student.
@@mariogalindoq The string's destructor isn't deleted, only the union's so you could explicitly call the destructor of the string in the union's destructor. However, this doesn't actually help as the union doesn't know if it has an active member. Instead the union's destructor should be empty for types that aren't trivially destructible and defaulted for types that are (this can be done using the trick that Jason shows) and calling the destructor of the active member of the union (if any) should be left to optional's destructor.
Imagine you have some iterator concepts concept forward_iterator = input_iterator && output_iterator concept bidirectional_iterator = forward_iterator && some_other_contraints concept random_access_iterator = bidirectional_iterator && some_other_constraints The lower concepts have all the constraints that the above ones have and more, thus they are more constrained. If you have different versions of an algorithm for each iterator concept and pass it an iterator from std::vector, then that iterator fulfills all three concepts above and the compiler will choose the version for random access iterator which is the most constrained. Hope that explains it...
@@pooya130 In that case you will get a hard error due to ambiguous instantiations since the compiler can't figure out which concept is the more constrained one.
Multiple destructors wtf. Your where able to shoutdown s whole CPP system back in the days when you made the biggest fail of all. Doing stuff in a destructor that triggers a exception. Worst case scenario where this went really bad was when this destructor was called when a exception was already thrown especially when you use smart pointer. So an exception thrown when the stack is already unwinding and a object gets destroyed was the death of the whole application.
I don't see the value in any of these videos.. you're teaching a syntax platform and the novices in your channel think that you're teaching programming.. tell them at least that learning C++ is nothing but learning paradigms designed by other human beings where if your novice viewer learn C (not C++) very well he will be able to author his own programming language instead because learning a programming language platform is not learning programming. This C++ 11/17/2x whatever is simply heading towards the wall in a very high speed. Also most of them think you're great because of your knowledge of C++ hahahaha tell them about your long career in C and Assembly!!!
Oh god, everytime I think it cannot possibly get more horrible new C++ features prove me wrong. Why do I need to to the compiler's work? The compiler knows the type (int) and knows that the destructor of an int does nothing, so the if (initialized) body also does nothing which means the whole destructor does nothing. As it's not virtual, it should be classified as trivial. All of this should work automatically without the need to re-implement it every single time. Disgusting.
@@broken_abi6973 I'm not sure if you read my post. C++ templates can be thought of as copy&paste with search&replace in the type, so a is different from a. Whether the destructors is user-defined or default should be irrelevant, as I've explained, as the compiler can analyze and optimize both "implementations" (instantiations) separately.
C++ is such a dead language. Literally the only use case for it is if you have to use DirectX on Windows. Sorry C++, you aren't the only girl at the dance anymore. Thankfully there are so many other languages that are better designed. Sorry for all you DirectX guys out there.
and how would you solve this particular problem? firstly, there is an optional in the standard C++ library, the problem does not need to be solved. secondly, if you need your own optional and you don't need additional performance, you can not use anything from the video, and write a simple implementation. moreover, this is library code, not user code. thirdly, if you really need exactly this behavior, what will other languages, in particular object-oriented ones, give you? to create an interface, different classes, one implements a trivially destructible one, the other is non-trivial. it's terrible to create so many entities instead of such a simple and compact solution as C++ offers - in C++, the corresponding solution will be written faster. and no, it's not dead, find out
One word wow.... C++ going so fast and so vast, it just feels like we always catching up
Holy shit Jason the learning curve only gets curvier.
we might get some c++ mastering speedrunning
"Choose! Choose the form of the destructor." -Ghostbusters
{ crossTheBeams(); } is non trivial
I've been out of the loop in C++ development for a few years already and I'm just learning that concepts finally made it into C++20. That, and there's GCC 11. Shit's bananas.
Was expecting some shenanigens, wasn't dissappointed,
Very intereseting thanks!
This is insanely good. Though it makes me sad that STL implementations may not use it for backward compatibility.
I suppose for backward compatibility, they could do it the inheritance way.
@@auxchar Or some ugly versioning macro and manage two implementations (msvc does that in some places.)
@@LesleyLai Msvc team plan to make a big update of their toolset to solve many known problems but require breaking changes. So this may happen in the future, but I don't know when. My guess is that it might happen when the STL will get modularized.
Can you please explain how that will break backword compatibility?
Macros
This is not "multiple destructors".
An Optional only has one destructor (a trivial destructor) and an Optional only has one destructor (a non trivial destructor)!
However there are multiple destructors in C++. Try to look at the generated assembler for a classes destructor for a class with non-trivial destructible members.
Wow, that is really cool
In any case you end up with user-declared destructor. In such case compiler does not generate implicit move constructor for you. Therefore, you have to define move constructor explicitly, otherwise your optional is not movable. So, is there a way to not define `~Struct() = default` at all?
Regarding the audio clicking sound. Have you tried increasing the buffer size in whatever software you use to record? If the buffer is too small, then every once in a while the computer won't be able to process a buffer completely in time and this results in random clicks in the recording. I've definitely been there. Super annoying, super random
Great video. Very well explained!
Black screen around 6:22, and again at around 10:03
@@cppweekly I'm on UA-cam Premium and yeah I get the black cuts as well.
@@void_p, same for me. Must be our ad blockers.
Standard UA-cam, no ad blocker here, seeing the black screens on chrome desktop
saw the ads and the black screen on mobile
Am on premium, mobile, saw two black screens. I am behind a PiHole though, but premium shouldn't even try to load ads. (Never seen this behaviour before)
Great episode. Finally some more advanced material in Weekly.
Really this should be called "Conditional Inheritance" ;)
Now I must have chosen the wrong Jason turner weekly. This one flew right over my head. I can't still wrap my head around its necessity. But, well, thanks anyway.
Because of the anonymous union the data destructor is not called I guess?
Or does the union serve another purpose?
Yeah. The purpose of the union is to circumvent the default construction of the value and the unconditional invocation of the value destructor since we don't know whether an optional contains a value at compile time. Also, an optional can contain types without a default constructor.
Can this be applied to any function member or only to destructors?
The require clause can be applied to any function (member and non-member).
That's actually pretty awesome, thanks for sharing!
I don't get, how you access the destructor of data in the struct optional_nontrivial. (9:45 at line 8)
The union with the data member is defined in optional, and optional inherits from optional_nontrivial not the other way round.
The static_cast casts the this ptr to the derived type that's injected via template param, that's why data is accessible. This technique is called static polymorphism. Neat little trick.
using CRTP
Great video. Btw seems like some portions you were holding arrow keys to move characters. Ctrl+arrow (I think alt+arrow on mac) jumps by word.
Thanks Jason for this insight..!!
Really nice and to the point video.
Apparently, code on 9:40 can't be compiled with Optional being instantiated. Because of the union for non-trivial destructible type and because of line 8 where unknown name Contained being used. But that is not my question, my question is - in the presented shape code does compile, that mean that gcc does not fully figured out how exactly you can destruct the object, but already knew that is is not trivially destructible. That level of laizyness I was not expecting, do you have any other examples or explainations how it works under the hood?
Whoa! Mind blown!
Instead of conditional inheritance, wouldn't enable_if be enough? And it would simulate the requires version mopre closely.
Not naively, SFINAE would not work in this case, because "Contained" is a template parameter of the "Optional" class template and not of a member function (destructor) template. One way to make it work with SFINAE would be to templatize the destructor using a dummy template parameter that default to "Contained":
template
class Optional {
...
template
~Optional() {
...
}
}
@@xavierthomas1980 Destructors can't be templated, i get this error : "error: destructor 'Optional::~Optional()' declared as member template".
How is Optional_nontrivial able to use static_cast(this)->data.~Contained()? Is that SFINAE? Or is just the case that it doesn't matter at that level of the file because it is still a template? It can only generate an error if during template substitution it fails to compile? It feels like a dependency that breaks encapsulation to me.
SFINAE works only at the signature.
It doesn't matter because it's a template. When used, Contained should be complete.
There are so many cases where you have to carefully analyse the generated assembly to understand which overload actually gets chosen.
I like all your c++posts. But one thing I don't like, or say a suggestion, is that I have always to watch them on a big monitor. The font is way too small for my phone, even too small for my 12 inches ipad. I hope all your future post at least good for watching on ipad. Thankd
Doesn't the operator= require a placement new in case the Optional is uninitialized?
(If the machinery is there, it should also work for non-trivial classes to really illustrate the point.)
Let's make the C++ language even more complex. Isn't it possible that the C++ compiler itself detects that a C++ destructor only does trivial stuff and can therefore be subject to optimizations?
I thought this was going to be about ::operator delete and how compilers create two versions of destructors with virtuals
Compare optimization provided by compilers like visual c++, gcc, clang
if you have non trivially destructible type, then two destructors are valid for this type, how compiler choose one?
nope, only one is valid, since requires is not satisfied.
🔥
Why the union?
could this not be done with explicit specialisation using TSFINAE?
Destructors take no arguments nor return any, and aren't templated.
7:30
The code in line 7 shouldn't work, should it?
static_cast(this)->data.~Contained();
The compiler doesn't know the identifier 'Contained' at that moment.
(wrong answer removed)
Are you thinking of "it misses typename to disambiguate"? Some have been removed because not necessary.
@@MaceUA No this code is not correct, I could put there `~X` and it still not have error as this code it not used any where.
Even more, I do not think that template arguments names have any effect on any thing outside given template scope:
godbolt.org/z/aarc1M hard error when function i used.
@@von_nobody You're not doing the same as Jason did. You're inheriting the other way around. In your case F should be the base class, not the derived.
@@WouterStudioHD This should not matter, try doing this is godbolt and see results, overall I come to conclusion that destruction name could be named same as any other function, only in case of template scope C++ can replace template argument name with correct type.
Result is that if destructor function name match class then code will compile, other wise you will have error.
Andrey Alexandresku talk about conxtexpr if just came up to my mind. That could easily be written as
if constexpr (someting) {
~Optional() {}
}
Sadly that is not a thing in C++. Hope Metaclasses/codegen will help a little bit.
@Ziggi Mon Using *same* syntax - yeah ,totally a bad idea. Just a reminder we have no such a power yet.
You are remembering an old version of constexpr if, when it was allowed to be used without introducing a scope. It had fearsome backlash from Stroustrup and few others.
Great video, how come you don't need the default ctor to require default destructibility though?
Why?
@@akj7 So we only ever define one dtor. Isn't it an error to have 2 dtors defined?
@@dennisrkb These are not two destructors. He used CRTP to inherit from a base class that has one and the only destructor.
At compile time, C++ detects whether or not the template parameter is trivially destructible then inherits from the appropriate class, which has the destructor.
All this seem like the class has multiple destructors but it doesn't. This is clear, because : template class MyClass {}; is not a class. It generates different classes (signatures) according to the template parameter. So Optional and Optional like shown in the video will generate 2 different class signatures. This is also proven by the fact that you can't treat one as the other in for example a copy constructor.
Now to your main question:
Imagine:
class MyClass
{
int a;
public:
Myclass(int _a): a{_a} {}
~MyClass() = default;
};
MyClass is not default constructible but it is default destructible.
@@akj7 thx for the elaborate answer. My confusion is around 11:53. Optional is a class with 2 dtors, one defaulted, the other conditionally defined. How does this compile if the condition is true and both definitions are present?
@@dennisrkb Oh i see. Notice the 'constexpr' before the destructors? Notice the 'require'? Just like the explanation above, if the compiler notices that the template parameter is not trivially destructible, the first destructor is enabled (default destructors don't appear in the assembly output, meaning they are ignored), if not only the default constructor is available. All this is set at compile time.
Great!
Nice. Huge readability boost. Are there uses of this that'd come up other than wrapper/container types?
Jason: thank you for another good video, however std::string seems not to be a good example because you can't have a union containing a non-static data member with a non-trivial special member function. Therefore, creating an Optional with string fails. Any comment?
You can have a member with a special member function in a union since c++11. However, the special member function will be deleted on the union if you don't implement it yourself. I don't know how this would work for an anonymous union though, as you need a name to define a destructor.
@@IsaacClancy That's true. However even having a union name, it would be very difficult to implement a destructor for std::string because it would be necessary that you know very intricate details about the std library implementation. I think that Jason can suggest another example. At the end, what he want to show, and for many has been show very well, do not depends on the std library. Anyway, the current example has the fault that if you implement it as it is, it will give you a core dumped, creating confusion for a student.
@@mariogalindoq The string's destructor isn't deleted, only the union's so you could explicitly call the destructor of the string in the union's destructor. However, this doesn't actually help as the union doesn't know if it has an active member. Instead the union's destructor should be empty for types that aren't trivially destructible and defaulted for types that are (this can be done using the trick that Jason shows) and calling the destructor of the active member of the union (if any) should be left to optional's destructor.
Not really multiple destructors though, since templated code is just a code generator, and every generated class only has one destructor
It looks like you accidentally added this to the playlist twice.
I expected something like destructors be qualified with && for temporary objects. Or maybe it is quite useless idea.
What does "most constrained" mean?
Imagine you have some iterator concepts
concept forward_iterator = input_iterator && output_iterator
concept bidirectional_iterator = forward_iterator && some_other_contraints
concept random_access_iterator = bidirectional_iterator && some_other_constraints
The lower concepts have all the constraints that the above ones have and more, thus they are more constrained. If you have different versions of an algorithm for each iterator concept and pass it an iterator from std::vector, then that iterator fulfills all three concepts above and the compiler will choose the version for random access iterator which is the most constrained. Hope that explains it...
@@semirvrana8501 I'm OK with that. I am wondering about cases where constraints are not a subset of one another. What happens in that case?
@@pooya130 In that case you will get a hard error due to ambiguous instantiations since the compiler can't figure out which concept is the more constrained one.
@@semirvrana8501 That makes sense. Thanks!
Cool
Is C++ a real computer language, or is it a religion with its own priesthood and (obscure) theology?
Why not both?
"You can make a religion out of this"
Crazy 😝
this is amazing, but it makes me hate my life.
I thought you said never use std::move :)
when did he say that?
The ads are horrible. The content is great, Jason, but the interruptions are unbearable.
Seems like maybe this language is a little too complex
Urgh, do you REALLY need to embody more than one advert in your videos?
Hello.im interesting your content
Multiple destructors wtf.
Your where able to shoutdown s whole CPP system back in the days when you made the biggest fail of all.
Doing stuff in a destructor that triggers a exception.
Worst case scenario where this went really bad was when this destructor was called when a exception was already thrown especially when you use smart pointer.
So an exception thrown when the stack is already unwinding and a object gets destroyed was the death of the whole application.
This looks like the most ugly code I've seen in years. What only happened to good C++? With every new concept the language deteriorates. JMHO.
I don't see the value in any of these videos.. you're teaching a syntax platform and the novices in your channel think that you're teaching programming.. tell them at least that learning C++ is nothing but learning paradigms designed by other human beings where if your novice viewer learn C (not C++) very well he will be able to author his own programming language instead because learning a programming language platform is not learning programming. This C++ 11/17/2x whatever is simply heading towards the wall in a very high speed. Also most of them think you're great because of your knowledge of C++ hahahaha tell them about your long career in C and Assembly!!!
Oh god, everytime I think it cannot possibly get more horrible new C++ features prove me wrong.
Why do I need to to the compiler's work? The compiler knows the type (int) and knows that the destructor of an int does nothing, so the if (initialized) body also does nothing which means the whole destructor does nothing. As it's not virtual, it should be classified as trivial.
All of this should work automatically without the need to re-implement it every single time. Disgusting.
but the compiler knows most of the time and deduces it. The problem here is that in the string case, the destructor needs to be user-defined.
@@broken_abi6973 I'm not sure if you read my post.
C++ templates can be thought of as copy&paste with search&replace in the type, so a is different from a.
Whether the destructors is user-defined or default should be irrelevant, as I've explained, as the compiler can analyze and optimize both "implementations" (instantiations) separately.
@@xnoreq ah i see. nevermind then
C++ is such a dead language. Literally the only use case for it is if you have to use DirectX on Windows. Sorry C++, you aren't the only girl at the dance anymore. Thankfully there are so many other languages that are better designed. Sorry for all you DirectX guys out there.
ok boomer xd
and how would you solve this particular problem? firstly, there is an optional in the standard C++ library, the problem does not need to be solved. secondly, if you need your own optional and you don't need additional performance, you can not use anything from the video, and write a simple implementation. moreover, this is library code, not user code. thirdly, if you really need exactly this behavior, what will other languages, in particular object-oriented ones, give you? to create an interface, different classes, one implements a trivially destructible one, the other is non-trivial. it's terrible to create so many entities instead of such a simple and compact solution as C++ offers - in C++, the corresponding solution will be written faster. and no, it's not dead, find out