Klaus is capable of giving so much information in such a short amount of time without being overwhelming. You know a talk is going to be great if he gives it.
ahhhhh so THIS is type-erasure. I've always called it one of: - "the way C++ does dynamic dispatch like Common Lisp's defgeneric/defmethod" - "The thing Sean Parent introduces in that talk". This is MUCH shorter. Thanks, great talk, great explanation
26:00 "std::variant is the opposite of type erasure. std::variant [gives you] a closed set of types and an open set of operations. Type Erasure gives you an open set of types and a close set of operations. "
I see that you end up with different native functions accepting (const-ref to) "leaf" classes (like Circle, Square). Those "strategy" functions will need to access member data indirectly via "getters", i.e. one getter per data member. I always found that classes filled with getters somehow breaks encapsulation. For classes as simple as circle and square, it's no big deal. But for more complex classes involving multiple data members, you will end up revealing several specific implementation details via those getters. I know TE wants to solve a problem, but it seems to do so at the cost of breaking encapsulation (i.e., revealing implementation details) of the fundamental classes. Fell free to share you opinions.
External polymorphism seems to be what other languages have landed on, e.g. Go's interfaces or Haskell's typeclasses. It makes so much sense to not tightly couple data inheritance with functionality inheritance (what we get with virtual functions), as the former is not always needed. This is definitely my favorite type erasure talk this far!
Seems to me if you're going to critique the simple inheritance model for having MetalBigEndianCircle *and* MetalLittleEndianCircle your final solution should support that too.
The 'draw' and 'serialize' functions from the 'Shape' class should have example parameters entered instead of /*...*/. Eg another TE wrapper, 'Renderer'. Without it, you can't see at first glance how 'Shape' allows you to choose/change an implementation (which is easily noticeable when using a Strategy pattern).
It is mentioned that when using "Strategy" pattern performance is reduced due to many small, manual allocations. I think these small allocations are implicitly done also in case of using type erasure technique when creating "ShapeModel" for each object. So, I guess, mentioning only about *manual* allocations should be enough.
Type erasure is great, but the final solution makes no mention of the original problem, which is namely using OpenGL or other graphics libraries with this shape ontology. It seems like as presented, this is really only solving half the problem which is that the Shape hierarchy is totally separate from the drawing hierarchy. But how to combine the drawing hierarchy in, is never explicitly explained in the final solution. There are a few that make sense to me (having an IRenderer interface, having n*m free functions like draw(const Circle&, OpenGlRenderer), draw(const Circle&, MetalRenderer&), draw(const Square&, OpenGlRenderer), etc). I believe the solution is there, but the presenter really should have gone the extra step to fully prove it.
He actually mentioned it in a sentence but I agree it could have been stated more prominently. The idea is that the draw function is a free function that can be implemented anywhere as long it is in the compilation unit at the end. So one solution would be to have a header for e.g. OpenGL where you would define all the draw functions for all of the shapes. Then you would create another header for e.g. Vulkan and implement the same functions there but for the Vulkan backend. With this approach you would either include the OpenGL or Vulkan header and thats it. The other idea would be to have libraries (again one for OpenGL, one for Vulkan) and link to the one you need.
Good talk on the merits in addition to the mechanics of Type Erasure! I wonder how the proposed "Deduce This pattern" helps or interacts with Type Erasure.
at 29:15, why is the T&& passed to the ShapeModel constructor a rvalue reference? I thought it's a forwarding/universal reference, because it's a template with double &
Ok, so I have a collection of Shape objects including shapes like Circle, Square and Triangle. Now I want to find the average radius of all the Circle objects. How do I do that? I'll need to loop over all the Shape objects in the collection. I don't want to have to put a GetRadius() function in the ShapeConcept class because it's irrelevant to the Square and Triangle classes. Some functions are common to all Shapes like draw() and serialize() but some are specific to a particular Shape. I may want to use those functions. Not easy to do with this type erasure pattern.
A question on the templated constructor. What if the constructor for Circle take a single parameter radius, but the Square constructor takes two, width and the height?
You pass to shape constructor a concrete shape concept instance. You will fully construct a square with two parameters and pass it to shape, or circle with one parameter. Does it answer your question?
Hi Klaus, the C++ community, and CppCon... I am trying to follow along with your slides and implement this toy as exploration into a design I'm working on. For this, I'm sticking with the same code structure, just changing the names to look more like my application. What I am running into is a compiler error I don't understand (MS Visual Studio 2019, uses C++14) And it doesn't like it when I instantiate the "shape" class with a, say a "circle" (again I'm using different names). The complaint is where I am instantiating the pimple in the templated constructor (introduced at 35:29 in the video) pimpl { new Model(x) } And I get "error C2664: 'Shape::Model::Model(T &&)': Can't convert argument 1 from 'const T' to 'T &&' on the line... What am I missing? You mentioned some other resources where you (or others) go into details of this implementation in other talks, but not in the notes in show notes. Hope you can point out my issue or point me to where the discussion is more details. Thanks in advance!
This seems very interesting but I’m having trouble reconciling both parts of the talk. The first part talks about a pattern for decoupling dependencies, via the Strategy pattern, and the second part presents a type erasure solution that is useful for collections of objects of different types that collectively conform to some interface. Where does the drawing strategy (e.g. OpenGL or Vulcan) fit into the TE design? Is the strategy passed into the Shape Draw function? How do the specific details of the actual shape (e.g. radius for circle) end up being accessible to an actual draw implementation? Or to swap it around, how does a Circle (which we can’t change) know anything about how to use, say, an OpenGL context to draw itself? I don’t see how erasing the type helps solve the original problem of decoupling the dependencies. What am I missing?
I think type erasure is not a very good name for it. I think it's really just an inverse bridge pattern. The type is still there. But the type now just applies to the implementation (the functions) rather than to the data (the structure, Circle, Square.) In the bridge pattern we've handed to the class the implementation it should use. In "type erasure", or inverse bridge, we've handed to the implementation the class or data it should use. So now we're passing around a ShapeConcept (OpenGL or Vulcan) with a Circle inside of it instead of passing around a Circle (i.e. its base class Shape) with an OpenGL or Vulcan implementation inside of it. What's the difference? I wondered that myself. But I think the important thing is that a type is better identified as what implementation or interface it supports than what data it has. So we separate out the data by (inverse bridge pattern) and just identify the type by it's implementation. When we pass a thing to a function we typically only want to know what functions it supports. So it's better when a type tells you what the implementation is rather than what the data is.
This is the first talk I've listened to on Type Erasure and Klaus beautifully lays down the concepts. Can you provide a link to the slides or the code used in the slides? PS : The description has a link to the repo for CppCon2020 and not 2021.
Great talk but doesn't compile (in VS2022 at least). It can't find the free functions for draw() and serialize(). If you rename these functions in ShapeConcept, e.g. to serialize_ and draw_, the compiler seems happier. The templated constructor for Shape doesn't appear to work either when you take the const lvalue reference, but seems happier with a forwarding reference.
For your problems (name-hiding and binding const lvalue ref to rvalue), the speaker corrects them in his 2022 CppCon talk. Please check P.19 and P.29 of cppcon.b-cdn.net/wp-content/uploads/2022/09/Type-Erasure-The-Implementation-Details-Klaus-Iglberger-CppCon-2022.pdf
How can I invoke the overridden clone() member function of the ShapeModel class, and who is responsible for calling it? Can you assist me in comprehending this?
I know I'm really late at this point and the question is probably very basic and laughable. But can some one tell me if instantiate such a templated class allocate heap memory all over the place? And how am I supposed to modify any derived classes since I don't know how could I access the members of the pass argument type.
Since the number of shape types is known at compile time, why not a tuple of vectors of shapes? In addition, non-instrusive serialization libraries are available, e.g. CEREAL.
Thanks for the great talk! I gave it a shot and tried to implement the example. However, the constructor of ShapeModel with T&& and std::forward did not work for me. Instead, I tried a T const& without std::forward, as you described in earlier talks. That did work just fine. The error was "cannot bind rvalue reference of type 'Circle&&' to lvalue of type 'const Circle'. Is the code shown on the slides actually working, or did I mess up?
Change Model ctor to a templated ctor with additional type parameter like "Model::template Model(U&& u)". The problem is that given a concrete type argument Circle for class template Model, the ctor shown in the slides will be instantiated as "Model::Model(Circle&&)", hence it won't accept anything other than rvalue (yes the ctor shown in the slide is broken). However, if the Model ctor is supplied with another type parameter U, then compiler could substitute U with "const Circle" and instantiate the ctor as intented like this: "Model::template Model(const Circle&)". A forwarding reference should be independently deduced.
You have to use a forwarding constructor with universal references and std::forward the arguments. However, if the forwarding constructor is templated, you also need to restrict it to work on all types except the type of the class, so that it doesn't shadow the copy constructor in overload resolution.
awesome but I'm worried that it has pimples , have to squeeze the pus out of them lol🤠 seriously completely blown away took a bit of concentration but it's really obvious when you get it
Unfortunately I don’t see any “beauty” in this implementation because this still requires us to write boilerplate code and I don’t want to use any library for such simple things that a language is supposed to provide.
1) he is not a native speaker; 2) many viewers are not native speakers; 3) this is supposed to be an talk for most levels of knowledge and intellect; 4) I listened to this on 1.5 just fine, it's easier to speed it up than to pause and go back constantly
Found the slides here: cppcon.digital-medium.co.uk/wp-content/uploads/2021/09/Type-Erasure-A-Design-Analysis.pdf Don't have the code though, but you can whip something up from the slides. I wish we had some working code too look at though.
Klaus is capable of giving so much information in such a short amount of time without being overwhelming. You know a talk is going to be great if he gives it.
Klaus repeatedly saying "However... we're not happy!" and continuing to strive for the ultimate perfection... he's definitely a German! :D
I appreciate the "conversational" approach he has in both his books and his talks.
ahhhhh so THIS is type-erasure.
I've always called it one of:
- "the way C++ does dynamic dispatch like Common Lisp's defgeneric/defmethod"
- "The thing Sean Parent introduces in that talk".
This is MUCH shorter. Thanks, great talk, great explanation
Nice talk! This is the most beautiful solution I have ever seen to solve the problem of a large number of class inheritances. Thanks, Klaus!
26:00 "std::variant is the opposite of type erasure.
std::variant [gives you] a closed set of types and an open set of operations.
Type Erasure gives you an open set of types and a close set of operations.
"
I see that you end up with different native functions accepting (const-ref to) "leaf" classes (like Circle, Square).
Those "strategy" functions will need to access member data indirectly via "getters", i.e. one getter per data member.
I always found that classes filled with getters somehow breaks encapsulation.
For classes as simple as circle and square, it's no big deal. But for more complex classes involving multiple data members, you will end up revealing several specific implementation details via those getters.
I know TE wants to solve a problem, but it seems to do so at the cost of breaking encapsulation (i.e., revealing implementation details) of the fundamental classes.
Fell free to share you opinions.
Klaus gives us an elegant and very comprehensive speech, as always!
Dear Mr. Klaus, you are a excellent teacher.
This lecture indeed brought a smile to my face.
Thank you.
External polymorphism seems to be what other languages have landed on, e.g. Go's interfaces or Haskell's typeclasses. It makes so much sense to not tightly couple data inheritance with functionality inheritance (what we get with virtual functions), as the former is not always needed.
This is definitely my favorite type erasure talk this far!
Claus communicate thing so structured and clear. So cool!
Thanks Klaus this was a pleasure to watch! It is one of my favorite topics as well for the same reason.
Great to hear!
Thanks Klaus. Clear presentation of a beautiful idea. This motivates me to watch rest of Kluas' talks as well.
Very pleased to hear that you are inspired to watch further presentations.
As always great talk. Thanks Klaus!
Seems to me if you're going to critique the simple inheritance model for having MetalBigEndianCircle *and* MetalLittleEndianCircle your final solution should support that too.
Wow, this solves a problem I just encountered yesterday and used ugly void* casting. What perfect timing!
If Klaus was a professor at my university, I would have taken all his classes!
Amazing. This design pattern brings efficient existential types to the C++ world. Nicely presented as well.
Many thanks!
This is great, and I can't agree more that using inheritance as interface is one of the worst mistakes one can make in library design.
33:19 For those who missed, draw and serialize are implemented as free functions, which are referenced in the shape concept class polymorphically.
The 'draw' and 'serialize' functions from the 'Shape' class should have example parameters entered instead of /*...*/. Eg another TE wrapper, 'Renderer'.
Without it, you can't see at first glance how 'Shape' allows you to choose/change an implementation (which is easily noticeable when using a Strategy pattern).
I’m in fact trying to understand where the actual strategies would fit here
It is mentioned that when using "Strategy" pattern performance is reduced due to many small, manual allocations. I think these small allocations are implicitly done also in case of using type erasure technique when creating "ShapeModel" for each object. So, I guess, mentioning only about *manual* allocations should be enough.
This is such a great video on c++ design patterns! ❤
The answer was pod structs and free functions all along!
Type erasure is great, but the final solution makes no mention of the original problem, which is namely using OpenGL or other graphics libraries with this shape ontology.
It seems like as presented, this is really only solving half the problem which is that the Shape hierarchy is totally separate from the drawing hierarchy. But how to combine the drawing hierarchy in, is never explicitly explained in the final solution. There are a few that make sense to me (having an IRenderer interface, having n*m free functions like draw(const Circle&, OpenGlRenderer), draw(const Circle&, MetalRenderer&), draw(const Square&, OpenGlRenderer), etc). I believe the solution is there, but the presenter really should have gone the extra step to fully prove it.
I agree with you.
For the first solution that you proposed (IRenderer), don’t we fall into the same hierarchy problems?
He actually mentioned it in a sentence but I agree it could have been stated more prominently. The idea is that the draw function is a free function that can be implemented anywhere as long it is in the compilation unit at the end. So one solution would be to have a header for e.g. OpenGL where you would define all the draw functions for all of the shapes. Then you would create another header for e.g. Vulkan and implement the same functions there but for the Vulkan backend.
With this approach you would either include the OpenGL or Vulkan header and thats it. The other idea would be to have libraries (again one for OpenGL, one for Vulkan) and link to the one you need.
Good talk on the merits in addition to the mechanics of Type Erasure! I wonder how the proposed "Deduce This pattern" helps or interacts with Type Erasure.
Great pattern, and presented so easy to grasp.
Glad it was helpful!
Is the example code from this talk available anywhere ? I can#t get the examples in the .pdf to compile and I'm pulling my hair out...
Excited
at 29:15, why is the T&& passed to the ShapeModel constructor a rvalue reference? I thought it's a forwarding/universal reference, because it's a template with double &
It means the ownership is being transferred into the ShapeModel.
Ok, so I have a collection of Shape objects including shapes like Circle, Square and Triangle. Now I want to find the average radius of all the Circle objects. How do I do that?
I'll need to loop over all the Shape objects in the collection. I don't want to have to put a GetRadius() function in the ShapeConcept class because it's irrelevant to the Square and Triangle classes. Some functions are common to all Shapes like draw() and serialize() but some are specific to a particular Shape. I may want to use those functions. Not easy to do with this type erasure pattern.
A question on the templated constructor. What if the constructor for Circle take a single parameter radius, but the Square constructor takes two, width and the height?
Did you find an answer for that?
You pass to shape constructor a concrete shape concept instance. You will fully construct a square with two parameters and pass it to shape, or circle with one parameter. Does it answer your question?
Hi Klaus, the C++ community, and CppCon... I am trying to follow along with your slides and implement this toy as exploration into a design I'm working on. For this, I'm sticking with the same code structure, just changing the names to look more like my application. What I am running into is a compiler error I don't understand (MS Visual Studio 2019, uses C++14) And it doesn't like it when I instantiate the "shape" class with a, say a "circle" (again I'm using different names). The complaint is where I am instantiating the pimple in the templated constructor (introduced at 35:29 in the video) pimpl { new Model(x) } And I get "error C2664: 'Shape::Model::Model(T &&)': Can't convert argument 1 from 'const T' to 'T &&' on the line... What am I missing? You mentioned some other resources where you (or others) go into details of this implementation in other talks, but not in the notes in show notes. Hope you can point out my issue or point me to where the discussion is more details. Thanks in advance!
in a sibling comment hung biu suggests using Model::template Model(U&& u)
Excelent talk!
Glad you enjoyed it
Where could I get the PPT of the above presentation?
This seems very interesting but I’m having trouble reconciling both parts of the talk. The first part talks about a pattern for decoupling dependencies, via the Strategy pattern, and the second part presents a type erasure solution that is useful for collections of objects of different types that collectively conform to some interface. Where does the drawing strategy (e.g. OpenGL or Vulcan) fit into the TE design? Is the strategy passed into the Shape Draw function? How do the specific details of the actual shape (e.g. radius for circle) end up being accessible to an actual draw implementation? Or to swap it around, how does a Circle (which we can’t change) know anything about how to use, say, an OpenGL context to draw itself?
I don’t see how erasing the type helps solve the original problem of decoupling the dependencies. What am I missing?
I think type erasure is not a very good name for it. I think it's really just an inverse bridge pattern. The type is still there. But the type now just applies to the implementation (the functions) rather than to the data (the structure, Circle, Square.)
In the bridge pattern we've handed to the class the implementation it should use. In "type erasure", or inverse bridge, we've handed to the implementation the class or data it should use. So now we're passing around a ShapeConcept (OpenGL or Vulcan) with a Circle inside of it instead of passing around a Circle (i.e. its base class Shape) with an OpenGL or Vulcan implementation inside of it.
What's the difference? I wondered that myself. But I think the important thing is that a type is better identified as what implementation or interface it supports than what data it has. So we separate out the data by (inverse bridge pattern) and just identify the type by it's implementation. When we pass a thing to a function we typically only want to know what functions it supports. So it's better when a type tells you what the implementation is rather than what the data is.
Amazing talk!
This is the first talk I've listened to on Type Erasure and Klaus beautifully lays down the concepts.
Can you provide a link to the slides or the code used in the slides?
PS : The description has a link to the repo for CppCon2020 and not 2021.
Thank you for bringing this to our attention. We are in the process of remedying this incorrect link.
@@CppCon Any news on this?
@@CppConany news on that?
Great talk but doesn't compile (in VS2022 at least).
It can't find the free functions for draw() and serialize(). If you rename these functions in ShapeConcept, e.g. to serialize_ and draw_, the compiler seems happier.
The templated constructor for Shape doesn't appear to work either when you take the const lvalue reference, but seems happier with a forwarding reference.
For your problems (name-hiding and binding const lvalue ref to rvalue), the speaker corrects them in his 2022 CppCon talk. Please check P.19 and P.29 of cppcon.b-cdn.net/wp-content/uploads/2022/09/Type-Erasure-The-Implementation-Details-Klaus-Iglberger-CppCon-2022.pdf
How can I invoke the overridden clone() member function of the ShapeModel class, and who is responsible for calling it? Can you assist me in comprehending this?
I know I'm really late at this point and the question is probably very basic and laughable.
But can some one tell me if instantiate such a templated class allocate heap memory all over the place?
And how am I supposed to modify any derived classes since I don't know how could I access the members of the pass argument type.
wonderful talk, i think external polymorphism is similar to crtp
I am inspired!
Amazing talk
Since the number of shape types is known at compile time, why not a tuple of vectors of shapes? In addition, non-instrusive serialization libraries are available, e.g. CEREAL.
Slides are not in your Github page. I cannot access it.
OMG, I see Dr. Douglas Schmidt here :)
Hm. "We dont have to deal with inheritance anymore". We still have virtual dispatch, dpnt we?
Thanks for the great talk! I gave it a shot and tried to implement the example. However, the constructor of ShapeModel with T&& and std::forward did not work for me. Instead, I tried a T const& without std::forward, as you described in earlier talks. That did work just fine. The error was "cannot bind rvalue reference of type 'Circle&&' to lvalue of type 'const Circle'. Is the code shown on the slides actually working, or did I mess up?
you are right,
Change Model ctor to a templated ctor with additional type parameter like "Model::template Model(U&& u)". The problem is that given a concrete type argument Circle for class template Model, the ctor shown in the slides will be instantiated as "Model::Model(Circle&&)", hence it won't accept anything other than rvalue (yes the ctor shown in the slide is broken). However, if the Model ctor is supplied with another type parameter U, then compiler could substitute U with "const Circle" and instantiate the ctor as intented like this: "Model::template Model(const Circle&)". A forwarding reference should be independently deduced.
You have to use a forwarding constructor with universal references and std::forward the arguments. However, if the forwarding constructor is templated, you also need to restrict it to work on all types except the type of the class, so that it doesn't shadow the copy constructor in overload resolution.
awesome!
Thanks!
Hi, guys! What design patterns are used in Type Erasure?
Also recommend checking out the talk "Runtime Polymorphism: Back to the Basics" from Louis Dionne who is the author of dyno
And Sean Parents talk (on which o think Louis' is based or credits), better code: polymorphism
Is this technique equivalent to type traits in rust?
90% sure it is, or at least they achieve the same means, i am not sure about implementation details
thank you
You're welcome
This appears to be almost exactly a C++ implementation of the Rust trait system's fat-pointer type erasure.
Gonna totally make a better example than shapes
awesome but I'm worried that it has pimples , have to squeeze the pus out of them lol🤠
seriously completely blown away
took a bit of concentration but it's really obvious when you get it
really clever love the loose coupling
Why not use variant instead of type erasure ?
Unfortunately I don’t see any “beauty” in this implementation because this still requires us to write boilerplate code and I don’t want to use any library for such simple things that a language is supposed to provide.
Interesting topic, incredibly slow delivery. This could've easily fit into 20 minutes just like the first talk about the topic he mentioned.
1) he is not a native speaker; 2) many viewers are not native speakers; 3) this is supposed to be an talk for most levels of knowledge and intellect; 4) I listened to this on 1.5 just fine, it's easier to speed it up than to pause and go back constantly
This is so gross
Good god I hope they never build this into the language. Just code in python or JavaScript if you want to erase your types.
Found the slides here: cppcon.digital-medium.co.uk/wp-content/uploads/2021/09/Type-Erasure-A-Design-Analysis.pdf
Don't have the code though, but you can whip something up from the slides. I wish we had some working code too look at though.
Hero