C++ Weekly - Ep 259 - CRTP: What It Is, Some History and Some Uses
Вставка
- Опубліковано 28 лип 2024
- ☟☟ Awesome T-Shirts! Sponsors! Books! ☟☟
Upcoming Workshop: C++ Best Practices, NDC TechTown, Sept 9-10, 2024
► ndctechtown.com/workshops/c-b...
Upcoming Workshop: Applied constexpr: The Power of Compile-Time Resources, C++ Under The Sea, October 10, 2024
► cppunderthesea.nl/workshops/
T-SHIRTS AVAILABLE!
► The best C++ T-Shirts anywhere! my-store-d16a2f.creator-sprin...
WANT MORE JASON?
► My Training Classes: emptycrate.com/training.html
► Follow me on twitter: / lefticus
SUPPORT THE CHANNEL
► Patreon: / lefticus
► Github Sponsors: github.com/sponsors/lefticus
► Paypal Donation: www.paypal.com/donate/?hosted...
GET INVOLVED
► Video Idea List: github.com/lefticus/cpp_weekl...
JASON'S BOOKS
► C++23 Best Practices
Leanpub Ebook: leanpub.com/cpp23_best_practi...
► C++ Best Practices
Amazon Paperback: amzn.to/3wpAU3Z
Leanpub Ebook: leanpub.com/cppbestpractices
JASON'S PUZZLE BOOKS
► Object Lifetime Puzzlers Book 1
Amazon Paperback: amzn.to/3g6Ervj
Leanpub Ebook: leanpub.com/objectlifetimepuz...
► Object Lifetime Puzzlers Book 2
Amazon Paperback: amzn.to/3whdUDU
Leanpub Ebook: leanpub.com/objectlifetimepuz...
► Object Lifetime Puzzlers Book 3
Leanpub Ebook: leanpub.com/objectlifetimepuz...
► Copy and Reference Puzzlers Book 1
Amazon Paperback: amzn.to/3g7ZVb9
Leanpub Ebook: leanpub.com/copyandreferencep...
► Copy and Reference Puzzlers Book 2
Amazon Paperback: amzn.to/3X1LOIx
Leanpub Ebook: leanpub.com/copyandreferencep...
► Copy and Reference Puzzlers Book 3
Leanpub Ebook: leanpub.com/copyandreferencep...
► OpCode Puzzlers Book 1
Amazon Paperback: amzn.to/3KCNJg6
Leanpub Ebook: leanpub.com/opcodepuzzlers_book1
RECOMMENDED BOOKS
► Bjarne Stroustrup's A Tour of C++ (now with C++20/23!): amzn.to/3X4Wypr
AWESOME PROJECTS
► The C++ Starter Project - Gets you started with Best Practices Quickly - github.com/cpp-best-practices...
► C++ Best Practices Forkable Coding Standards - github.com/cpp-best-practices...
O'Reilly VIDEOS
► Inheritance and Polymorphism in C++ - www.oreilly.com/library/view/...
► Learning C++ Best Practices - www.oreilly.com/library/view/... - Наука та технологія
As others have pointed out on Twitter, this is not necessarily "Best Practices" for using CRTP. My goal was to show what the concept is and potential use cases. There are MANY possible uses.
Check out this Twitter note from Bjorn Fahller for more info twitter.com/bjorn_fahller/status/1361362603126251525
It was James Coplien who coined the pattern, og described it in the magazine C++Report in the mid 90's.
His amazing book "Multi-Paradigm DESIGN for C++" provides a solid design foundation for CTRP (among other things).
It is very similar to pure virtual functions, declared in the base class and defined in the derived classes, but bound at compile time instead of run time.
I couldn't find any use of CRTP in Coplien's earlier book "Advanced C++" published in 1992. The Multi-Paradigm book dates from 1998. Even there, he doesn't name the pattern. So, I wonder who coined the term.
It was coined by Jim Coplien (who I personally know) i an article in C++ Report, februar 1995. The article was named "Curiously Recurring Template Pattern"
Great talk. This is perhaps my favorite pattern; it's incredibly useful.
CRTP is great. I started in C++ using ATL for my own things so it's kind or normal for me. These days I think it's a good fit for embedded programming, if you have multiple embedded modules in a project you use it to split common logic off from hardware interaction.
inline Derived_T& derived_me() {return static_cast(*this);} // is a handy function
will there be an episode on .mpp C++ modules?
I second this
I fourth this
I CRTP
I fifth this
I sixth this
This was really a nice example.
It would nice to mention that CRTP is also called static polyformismus and one reason why it is used is to avoid the cost of virtual functions.
That makes no sense.
static dispatch vs dynamic dispatch are different things, with different capabilities and pros and cons. You don't use static dispatch to "avoid the cost of virtual functions". you use dynamic dispatch when it's appropriate to do so, same with static dispatch. You can only use static dispatch (which would be this) if you can know the specific type of the data at compile time.
@@lucy-pero No, they are not the same thing. Everything you have written is wrong.
@@akj7 what
@@lucy-pero I misread your first comment. Concerning "avoid the cost of virtual functions", you replied: "you use dynmaic dispatch when its appropriate". Agreed, but how can the user know what is appropriate? Someone coming from Java or C# or doesn't know what appropriate is, would implement a pure virtual class and inherit from it to ascertain consistencies. Here static polymorphismus could have work nicely as there is no need to pay the price of dynamic dispatch.
@@akj7 sure, if you don't even know what you're doing then yeah it'd be nice to learn that there are 2 different ways of doing some things haha.. I'd just argue that this CRTP thing is horrible and i'd do it in some other way or just switch to Rust but I believe this about most C++ things....
I used CRTP a while ago to implement a "mixin" providing a stack allocated object pool of "typename CRTP" (and size).
Have used it recently to know whether some virtual functions were defined in inherited classes in a non-intrusive way:
struct IBase
{
virtual bool HasFunc() const = 0;
virtual void Func() {}
};
template
struct BaseT : public IBase
{
virtual bool HasFunc() const override
{
return !std::is_same_v;
}
}
This allows me to detect if Func() has been overloaded and do things accordingly at call site.
llvm/clang appears to use this quite a bit within the AST code
what about a deletion of specific constructor or function with base crtp template class?..
It should be renamed "curiously *recursive* template pattern" since it not so much recurring nowadays.
But it's also not recursive
@@cppweekly Well, it's a class inheriting another that is defined in terms of the first, isn't it ?
But yeah i'm stretching the definition. It may be thought as recursive on a conceptual level, as a type referring to itself in its definition. In practice, it's no more recursive than a class holding a pointer to itself. So not recursive, really.
When would you correctly use that? When I learn about some new and cool for me thing I immediately try to put it in use into my code base and I ruin everything. So, this time before I destroy my sanity I want to check for a proper use case. I know it could be used for static polymorphism, but when is that a good idea?
I think the correct answer in C++, 95% of the time, is when a given technique (CRTP, whatever) results in the most simple, most maintainable code, then it's the right time to use that technique.
The trick is knowing all the possibilities and be willing to find the best one when working on new code.
The most famous example of CRTP pattern is std::enable_shared_from_this. But occasionally nobody remember enable_shared_from_this class in CRTP context.
There was some reason I was irritated about std::enable_shared_from_this because there's a line in the standard that explicitly requires std::shared_ptr to do the wrong thing in a certain case, but I have now completely forgotten what that behavior even was, because I've only used shared_ptr like twice in total and just never really needed std::enable_shared_from_this. Anyways, as far as it goes it is a good example of using CRTP for something other than just operators.
@@killerbee.13 enable_shared_from_this widely and naturally used in async applications like boost::asio based servers. Common problem with this class is not freeing memory until last weakptr alive.
hi, you probably have forgot to add this episode to the c++ weekly playlist
Wait so you can update how an operator works?! Awesome!
A thing about this that I ran across once, is that there's no compile message for 'doing the wrong downcast', as long as the compiler knows how to do the Base -> Derived cast that you're telling it to do. Such as specifying an existing class derived from the Base for the CRTP parameter. This can easily happen if a copy-paste error is made from an existing derived class' definition, which is especially common when using this for 'mixins'. eg godbolt.org/z/8erWej
you can code generic singleton using this pattern
Not that it matters, but isn't the 2nd condition in operator== incorrect? Shouldn't it be `&& !( ...rhs < ...lhs )`?
What he wrote was !(a < b || b < a), which is equivalent to what I assume you are suggesting, that is !(a < b) && !(b < a).
De Morgan's laws for future readers
@@SagaxCorvinus You're correct. My eyes misplaced a parenthesis. The static casts and long lines make that hard to read 😅
@@su1cidity, and that's why prettier, better-formatted and better-structured code is always better! Aesthetics and readability matters in programming, even though the compiler doesn't care.
At first I read it wrong as well, because I didn't notice where the first parenthesis was closed. Code like this gets confusing without proper formatting.
Unreal Engine uses CRTP to implement TSharedFromThis, so a class can provide a shared pointer to itself
So when a polymorphic type knows the type it will get , then it can use CRTP?
Hi Jason,
Loving your videos, thank you.
Im not seeing the advantage hereat all (its my problem, not yours) ;)
If we removed templates altogether from this code and MyStruct just inherited from SomeTemplate (no template parameters anywhere, this code would still work and be much easier to read and work with IMHO).
Is CRTP adding the fact that the static_cast is definitely safe. Actually now that I'm writing this this question, I realise that without templates, "SomeTemplate" could be the base class of ANYTHING, so the static casts wouldn't work.
I've just answered my own question. Doh!
Anyways, great videos
What is the benefit to doing this over writing the comparisons before the spaceship operator? Is it just that it saves you from bloating your source code as well as saving time because you only have to provide the less than comparison?
It's kind of the opposite of an interface. Implementation is provided in the base class, so functionality can be injected into a class.
For instance, I wrote a linear algebra library based on this pattern. There are base classes such as 'constructable', 'iterable', 'dimensionable', etc to provide implementation details. As a consequence, my matrix class is completely empty, and I can reuse code between "matrix", and "vector", and "tensor". And I can create all kinds of optimizations by defining "diagonal_matrix", "expression", in just a few lines.
So if done correctly, it's efficient, saves a lot of code, and quite honestly allows you to organize member functions/constructors/etc for all your classes in one place, making the code much cleaner (in my opinion).
@@TomasPruzina-uw9ql In my workplace we use the latest standards for our simulators. The spaceship operator has arrived with C++20 so there's no reason for my workplace not to use it.
Initialisms are a subset of acronyms. Either term is acceptable here, though initialism is more precise.
It is the other way around. Not all initialisms are read as words. The subset of initialisms that are read as words are acronyms. Made-up words like 'Nasa' for NASA are allowed as acronyms; the usage is what defines them. e.g. Nobody reads 'Yoosa' for USA, so it is an initialism. CTRP is unreadable as a word ('Seeterp'?) so it is also an initialism.
@@technologicalwaste7612 When you pronounce it as a word it's called a "word acronym" which is another subset of acronyms. There are other ways to form acronyms than just getting the initial letters, which wouldn't include initialisms. Some people (like you) use the term "acronym" to only refer to word acronyms, and that's fine, too.
@@koyrehme4361 Would your definition of acronym cover abbreviations such as 'NiCad' for Nickel-cadmium battery?
@@technologicalwaste7612 Yes. Another good example is RADAR (RAdio Detection And Ranging).
Shouldn't you also delete the base class constructor to make the static_cast safer?
Static cast is always safe to use AFAIK
Cool explanation, but what's the advantage? Why not just define operator== in the original class?
Someone else much more competent than me will have to elaborate, but it's basically about decoupling logic from a data storage class. MyStruct should really only handle the int data member, so he moves the logic for equals to the mixin class.
It allows for code reuse. If you have a bunch of classes that need comparison operators, you now only need to write == / != / > etc one time in the base, and so you only have to provide the custom logic for each class in terms of the less than operator. So you save rewriting all the comparison operators.
This feels very hacky, but kinda interesting... Are there any other use cases than comparison operator overloading?
Jason showed the "quick and hacky way". Usually with CRTP you make utility functions for the static_cast. Biggest advantage of CRTP is static polimorphism I would say. You can sort of implement concepts with it. But just "sort-of" as concepts are way more convenient.
dunno if there might be a better way to do this than CRTP, but I've used this Singleton header in my own projects, originally seen here: github.com/ElDewrito/ElDorito/blob/master/ElDorito/Source/Utils/Singleton.hpp
So your child class can implement a simple static interface with:
static RetType DoXThing(){ return Instance().DoXThingImpl(); }
RetType DoXThingImpl(){ /* logic */ }
@@theRECONN Yeah, I thought about concepts as well, but haven't read that much about the subject.
It's a way for a derived class to customize its baseclass without using virtual functions (and their runtime overhead).
It's a bit cumbersome to use as the functions that the derived class should implement don't have a function signature listed anywhere, the baseclass just uses them. This is an issue for all kinds of templates.. concepts could help with this (but I don't have any experience with them)
A hack inside a black box is just a nice box. 😉
CRTP -- dark magic in C++
You seemed to have forgotten to put in the notes.
D'oh.
Recurring *Not* recursive. I made that mistake for many years.
I want to use this and make people scratch their heads the way I did !!!
Why is the operator== declared as `bool operator==(const SomeTemplate &rhs) const` and not as
`bool operator==(const CRTP &rhs) const`?
because it is defined inside SomeTemplate.
@@minirop SomeTemplate has CTRP as a template typename, so it can be directly used as argument or return type.
not sure, but maybe something for ADL?? I use your example...
@@maartenofbelgium I thought both parameters had to be of the same type. doesn't seem to be the case. but I never saw operator whose types were not the type of their class, so.
@@minirop std::vector operates on and returns items of type T/T&.
I tend to find CRTP annoying for code reuse, because it requires that the derived class members are public to be accessible from the base.
You should be able to declare the CRTP base class as a friend of the derived class that uses it, and then still use hiding in the derived class.
@@cppweekly True. However, seeing "friend" outside of library code disturbs me for some reason, and makes me reconsider the use of other techniques to tackle the problem.
Arent concepts replacing this?
Historical context is always useful, even if most use cases for CRTP go away.
Add some underscores, and make it a library
I’d guess that if someone is watching this in the distant future and doesn’t know why you’d go on a tangent about whether on-site training is a thing in January 2021, it might not be for them either, because you might have retired or something
if you happen to be watching this video in the distant future and you have no idea what 'on site' means...