digitalmars.D - Getting rid of dynamic polymorphism and classes
- Tommi (114/114) Nov 08 2012 I just started watching that:
- Jakob Ovrum (18/22) Nov 08 2012 For a lot of programs (or parts of programs) that currently use
- DypthroposTheImposter (7/7) Nov 08 2012 I also don't really like inheritance based design but..
- Tommi (12/16) Nov 08 2012 But the correct implementation depends on the required ownership
- F i L (17/17) Nov 08 2012 That's essentially how Go is designed:
- Ziad Hatahet (5/9) Nov 09 2012 ...
- Era Scarecrow (3/4) Nov 09 2012 If opDispatch is small&simple can/does it get inlined to the
- Iain Buclaw (5/9) Nov 09 2012 Potentially yes, but only with optimisation turned on. ;-)
- Era Scarecrow (2/7) Nov 09 2012 I thought so, and hope so :)
- Max Klyga (7/15) Nov 08 2012 Compiler can do a lot of optimizations with knowledge about classes.
- Tommi (7/9) Nov 08 2012 But there's no restrictive type hierarchy that causes unnecessary
- Marco Leise (13/24) Nov 10 2012 They work like this: Each object has as a pointer to a table
- Marco Leise (8/8) Nov 10 2012 P.S.:
- Tommi (13/20) Nov 10 2012 Is vftable essentially an array? So, it's just a matter of
- F i L (6/10) Nov 10 2012 Virtual functions have other performance limitations, naming they
- Era Scarecrow (6/8) Nov 09 2012 I've ripped the audio and done some processing to make it a
- Eldar Insafutdinov (6/6) Nov 09 2012 I have had the same thoughts for quite some time now -
- gnzlbg (1/1) Nov 11 2012 How would you create a vector of canvas and iterate over them?
- Tommi (56/57) Nov 11 2012 I assume you mean to ask "How'd you create a vector of shapes?".
- Tommi (22/23) Nov 11 2012 I should point out, just so that no-one is missing the obvious
- Tommi (77/79) Nov 14 2012 Here's the duck-typing design pattern they introduced in the
- Tommi (4/7) Nov 11 2012 I didn't mean to say 'similar', it's rather different. It
- Tommi (77/79) Nov 14 2012 Here's the duck-typing design pattern they introduce in the video
- Tommi (77/79) Nov 14 2012 Here's the duck-typing design pattern they introduce in the video
- Tommi (77/79) Nov 14 2012 Here's the duck-typing design pattern they introduce in the video
- Tommi (77/79) Nov 14 2012 Here's the duck-typing design pattern they introduce in the video
- Tommi (77/79) Nov 14 2012 Here's the duck-typing design pattern they intrdouced in the
- Tommi (77/79) Nov 14 2012 Here's the duck-typing design pattern they intrdouced in the
I just started watching that: http://cppnow.org/session/value-semantics-and-concepts-based-polymorphism/ ..and it got me thinking, couldn't we just get rid of dynamic polymorphism and classes altogether? Doesn't static polymorphism through the use of duck typing and member function delegates provide all that we need? This way we wouldn't have the nasty coupling of types which inheritance causes. Here's a C++ example: (I'm sure it would look nicer with D's syntax) // Canvas.h #pragma once #include <functional> #include <type_traits> #include <utility> #include <vector> class Canvas { private: struct Shape // Represents an interface { std::function<void (int x, int y)> resize; std::function<void (int x, int y)> moveTo; std::function<bool (int r, int g, int b)> draw; }; public: template <typename S> auto addShape(S& s) -> typename std::enable_if< std::is_same<decltype(s.resize(1, 1)), void>::value && std::is_same<decltype(s.moveTo(1, 1)), void>::value && std::is_same<decltype(s.draw(1, 1, 1)), bool>::value >::type { Shape shape; shape.resize = [&](int x, int y) { return s.resize(x, y); }; shape.moveTo = [&](int x, int y) { return s.moveTo(x, y); }; shape.draw = [&](int r, int g, int b) { return s.draw(r, g, b); }; _shapes.emplace_back(std::move(shape)); } Shape& getShape(size_t idx) { return _shapes[idx]; } private: std::vector<Shape> _shapes; }; // Circle.h #pragma once #include <conio.h> class Circle { public: void resize(int x, int y) { _cprintf("Circle resized to %d %d\n", x, y); } void moveTo(int x, int y) { _cprintf("Circle moved to %d %d\n", x, y); } bool draw(int r, int g, int b) { _cprintf("Circle drawn with color %d %d %d\n", r, g, b); return true; } }; // Rectangle.h #pragma once #include <conio.h> class Rectangle { public: void resize(int x, int y) { _cprintf("Rectangle resized to %d %d\n", x, y); } void moveTo(int x, int y) { _cprintf("Rectangle moved to %d %d\n", x, y); } bool draw(int r, int g, int b) { _cprintf("Rectangle drawn with color %d %d %d\n",r,g,b); return true; } }; // main.cpp int main() { Canvas canvas; Rectangle rectangle; Circle circle; canvas.addShape(rectangle); canvas.addShape(circle); canvas.getShape(0).resize(5, 5); canvas.getShape(0).moveTo(2, 3); canvas.getShape(0).draw(1, 12, 123); canvas.getShape(1).resize(10, 10); canvas.getShape(1).moveTo(4, 5); canvas.getShape(1).draw(50, 0, 50); _getch(); return 0; } // Prints: Rectangle resized to 5 5 Rectangle moved to 2 3 Rectangle drawn with color 1 12 123 Circle resized to 10 10 Circle moved to 4 5 Circle drawn with color 50 0 50
Nov 08 2012
On Thursday, 8 November 2012 at 17:27:42 UTC, Tommi wrote:..and it got me thinking, couldn't we just get rid of dynamic polymorphism and classes altogether? Doesn't static polymorphism through the use of duck typing and member function delegates provide all that we need?For a lot of programs (or parts of programs) that currently use runtime polymorphism, the answer seems to be yes, and Phobos is very good at helping D programmers do their polymorphism at compile-time. But dynamic polymorphism is special in that it is just that - dynamic. You can decide which implementation to use at runtime rather than having to do it at compile-time. When this runtime component is necessary, there is no replacement for runtime polymorphism. As for function pointers and delegates, class-based polymorphism provides a couple of additional niceties: for one, vtables are created at compile-time. Secondly, it provides a lot of syntax and structure to the system that you don't have with arbitrary function pointers or delegates. Emulating OOP (no, not Object *Based* Programming) with function pointers is a real pain. Without classes, we'd only be marginally better off than C in this area, thanks to delegates.
Nov 08 2012
I also don't really like inheritance based design but.. That example would crash hard if those stack allocated shapes were not in scope... Making it work safely would probably require std::shared_ptr usage So it uses way more memory per object(BAD) Just use a dynamic language like Lua, it doesn't have classes, you example would be dead simple in lua.
Nov 08 2012
On Thursday, 8 November 2012 at 17:50:48 UTC, DypthroposTheImposter wrote:That example would crash hard if those stack allocated shapes were not in scope... Making it work safely would probably require std::shared_ptr usageBut the correct implementation depends on the required ownership semantics. I guess with Canvas and Shapes, you'd expect the canvas to own the shapes that are passed to it. But imagine if, instead of Canvas and Shape, you have Game and Player. The game needs to pass messages to all kinds of different types of players, but game doesn't *own* the players. In that case, if a game passes a message to a player who's not in scope anymore, then that's a bug in the code that *uses* game, and not in the implementation of game. So, if Canvas isn't supposed to own those Shapes, then the above implementation of Canvas is *not* buggy.
Nov 08 2012
That's essentially how Go is designed: type Shape interface { draw() } type Circle struct { ... } type Square struct { ... } func (c *Circle) draw() { ... } func (s *Square) draw() { ... } func main() { var shape Shape var circle Circle var square Square shape = circle shape.draw() // circle.draw() shape = square shape.draw() // square.draw() }
Nov 08 2012
On Thu, Nov 8, 2012 at 10:36 AM, F i L <witte2008 gmail.com> wrote:That's essentially how Go is designed: type Shape interface { draw() }... Method dispatch is still done at runtime though. -- Ziad
Nov 09 2012
On Friday, 9 November 2012 at 21:06:16 UTC, Ziad Hatahet wrote:Method dispatch is still done at runtime though.If opDispatch is small&simple can/does it get inlined to the callers?
Nov 09 2012
On 9 November 2012 21:39, Era Scarecrow <rtcvb32 yahoo.com> wrote:On Friday, 9 November 2012 at 21:06:16 UTC, Ziad Hatahet wrote:Potentially yes, but only with optimisation turned on. ;-) -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';Method dispatch is still done at runtime though.If opDispatch is small&simple can/does it get inlined to the callers?
Nov 09 2012
On Friday, 9 November 2012 at 21:52:49 UTC, Iain Buclaw wrote:On 9 November 2012 21:39, Era Scarecrow <rtcvb32 yahoo.com> wrote:I thought so, and hope so :)If opDispatch is small&simple can/does it get inlined to the callers?Potentially yes, but only with optimization turned on. ;-)
Nov 09 2012
On 2012-11-08 17:27:40 +0000, Tommi said:..and it got me thinking, couldn't we just get rid of dynamic polymorphism and classes altogether?Compiler can do a lot of optimizations with knowledge about classes. Also it automates a lot things that would become boilerplate with proposed manual setup of delegates for each object.struct Shape // Represents an interface { std::function<void (int x, int y)> resize; std::function<void (int x, int y)> moveTo; std::function<bool (int r, int g, int b)> draw; };Dinamic polimorphism isn't gone anywhere, it was just shifted to delegates. This approach complicates thing to much and produces template bloat with no real benefit.
Nov 08 2012
On Thursday, 8 November 2012 at 21:43:32 UTC, Max Klyga wrote:Dinamic polimorphism isn't gone anywhere, it was just shifted to delegates.But there's no restrictive type hierarchy that causes unnecessary coupling. Also, compared to virtual functions, there's no overhead from the vtable lookup. Shape doesn't need to search for the correct member function pointer, it already has it. It's either that, or else I've misunderstood how virtual functions work.
Nov 08 2012
Am Thu, 08 Nov 2012 23:38:53 +0100 schrieb "Tommi" <tommitissari hotmail.com>:On Thursday, 8 November 2012 at 21:43:32 UTC, Max Klyga wrote:They work like this: Each object has as a pointer to a table of method pointers. When you extend a class, the new method pointers are appended to the list and existing entries are replaced with overrides where you have them. So a virtual method 'draw()' may get slot 3 in that table and at runtime it is not much more than: obj.vftable[3](); These are three pointer dereferences (object, vftable entry 3, method), but no search. -- MarcoDinamic polimorphism isn't gone anywhere, it was just shifted to delegates.But there's no restrictive type hierarchy that causes unnecessary coupling. Also, compared to virtual functions, there's no overhead from the vtable lookup. Shape doesn't need to search for the correct member function pointer, it already has it. It's either that, or else I've misunderstood how virtual functions work.
Nov 10 2012
P.S.: A) The more time is spent inside the virtual method, the less noticeable the impact of the lookup. B) If you call one virtual method often inside a single function, compilers typically cache the method pointer in some register. -- Marco
Nov 10 2012
On Saturday, 10 November 2012 at 09:23:40 UTC, Marco Leise wrote:They work like this: Each object has as a pointer to a table of method pointers. When you extend a class, the new method pointers are appended to the list and existing entries are replaced with overrides where you have them. So a virtual method 'draw()' may get slot 3 in that table and at runtime it is not much more than: obj.vftable[3]();Is vftable essentially an array? So, it's just a matter of offsetting a pointer to get access to any particular slot in the table? If virtual method calls are really that fast to do, then I think the idiom in the code snippet of my first post is useless, and the idiom they represent in that video I linked to is actually pretty great. Note: In order to make that video's sound bearable, you have to cut out the highest frequencies of the sound and lower some of the middle ones. I happened to have this "Realtek HD Audio Manager" which made it simple. Using the "city" filter helped a bit too. Don't know what it did.
Nov 10 2012
Tommi wrote:If virtual method calls are really that fast to do, then I think the idiom in the code snippet of my first post is useless, and the idiom they represent in that video I linked to is actually pretty great.Virtual functions have other performance limitations, naming they can't be inlined. So small virtual calls do have a big impact if used often, especially (so I hear) on ARM processors which don't have as advanced branch-prediction machinery as x86 (again, I'm just repeating what I've heard before).
Nov 10 2012
On Thursday, 8 November 2012 at 17:27:42 UTC, Tommi wrote:I just started watching that: http://cppnow.org/session/value-semantics-and-concepts-based-polymorphism/I've ripped the audio and done some processing to make it a little more understandable (without cranking the audio up to god awful levels); however I seem to have a little trouble uploading it somewhere accessible. I'll post a link when I get it uploaded (assuming anyone wants to make use of the audio rip...)
Nov 09 2012
I have had the same thoughts for quite some time now - library-based runtime polymorphism implementation. You can already do something like: http://pastebin.com/X2JFP1sD . However it is inefficient as you have to store thisptr for each delegate. One has to think about how to implement it more efficiently - some metaprogramming will be required.
Nov 09 2012
How would you create a vector of canvas and iterate over them?
Nov 11 2012
On Sunday, 11 November 2012 at 13:24:07 UTC, gnzlbg wrote:How would you create a vector of canvas and iterate over them?I assume you mean to ask "How'd you create a vector of shapes?". And I assume you mean "... by using the pointers-to-member-functions design pattern introduced in the first post". Notice that in the video I posted, they introduce a similar but cleaner design pattern, that uses virtual functions to accomplish the same goal. I think it's better to use that, given that virtual function calls aren't noticeably slower than calling through those member function pointers. But anyway, here's how you'd do it using the pointers-to-member-functions idiom: #include <conio.h> #include <functional> #include <memory> #include <type_traits> #include <utility> #include <vector> template <int typeId> class MyShape { public: void refresh() { _cprintf("MyShape<%d> refreshed\n", typeId); } }; struct Shape { std::function<void ()> refresh; template <typename S, typename _ = typename std::enable_if< std::is_same<decltype(S().refresh()), void>::value >::type> Shape(S&& s) : _s (new S(s)) { refresh = [&]() { return reinterpret_cast<S*>(_s.get())->refresh(); }; } private: std::unique_ptr<void> _s; // or std::shared_ptr }; int main() { std::vector<Shape> shapes; shapes.emplace_back(MyShape<2>()); shapes.emplace_back(MyShape<4>()); for (auto& shape : shapes) { shape.refresh(); } _getch(); return 0; } // Prints: MyShape<2> refreshed MyShape<4> refreshed
Nov 11 2012
I should point out, just so that no-one is missing the obvious here, that you'd be using static polymorphism through the use of template functions whenever it's possible, i.e. when you don't need to store polymorphic types. So, you'd do this: template <typename S> auto func(const S& shape) -> typename std::enable_if< is_shape<S>::value::type{ // ... } You would NOT do this: void func(const Shape& shape) { // ... } The big question is then: is the code bloat introduced by the massive use of template functions worth the speed gained through static polymorphism (vs. dynamic). Whether it is or not, the main gain for me is in de-coupling of types, which helps in writing truly generic code.
Nov 11 2012
Here's the duck-typing design pattern they introduced in the video I posted (in C++ again, sorry): #include <conio.h> #include <memory> #include <type_traits> #include <utility> #include <vector> class SomeShape { public: SomeShape() = default; explicit SomeShape(int volume) {} void draw() const { _cprintf("Drawing SomeShape\n"); } }; class OtherShape { public: OtherShape() = default; explicit OtherShape(int x, int y) {} void draw() const { _cprintf("Drawing OtherShape\n"); } }; class ShapeInterface { public: virtual void draw() const = 0; virtual ~ShapeInterface() {} }; template <typename T> class PolymorphicShape : public ShapeInterface { public: template <typename ...Args> PolymorphicShape(Args&&... args) : _shape (std::forward<Args>(args)...) {} void draw() const override { _shape.draw(); } private: T _shape; }; template <typename T, typename _ = void> struct is_shape : std::false_type {}; template <typename T> struct is_shape<T, typename std::enable_if< std::is_same<decltype(T().draw()), void>::value::type>: std::true_type {}; template <typename T> auto drawAgain(const T& shape) -> typename std::enable_if< is_shape<T>::value::type{ shape.draw(); } int main() { std::vector<std::unique_ptr<ShapeInterface>> shapes; shapes.emplace_back(new PolymorphicShape<SomeShape>(42)); shapes.emplace_back(new PolymorphicShape<OtherShape>(1, 2)); // Dynamic polymorphism: shapes[0]->draw(); // Prints: Drawing SomeShape shapes[1]->draw(); // Prints: Drawing OtherShape // Static polymorphism: drawAgain(SomeShape(123)); // Prints: Drawing SomeShape drawAgain(OtherShape(2, 4)); // Prints: Drawing OtherShape _getch(); return 0; }
Nov 14 2012
On Monday, 12 November 2012 at 05:49:55 UTC, Tommi wrote:Notice that in the video I posted, they introduce a similar but cleaner design pattern, that uses virtual functions to accomplish the same goal.I didn't mean to say 'similar', it's rather different. It accomplishes the same goal though, but using less memory and through the use of virtual functions.
Nov 11 2012
Here's the duck-typing design pattern they introduce in the video (in C++ again, sorry): #include <conio.h> #include <memory> #include <type_traits> #include <utility> #include <vector> class SomeShape { public: SomeShape() = default; explicit SomeShape(int volume) {} void draw() const { _cprintf("Drawing SomeShape\n"); } }; class OtherShape { public: OtherShape() = default; explicit OtherShape(int x, int y) {} void draw() const { _cprintf("Drawing OtherShape\n"); } }; class ShapeInterface { public: virtual void draw() const = 0; virtual ~ShapeInterface() {} }; template <typename T> class PolymorphicShape : public ShapeInterface { public: template <typename ...Args> PolymorphicShape(Args&&... args) : _shape (std::forward<Args>(args)...) {} void draw() const override { _shape.draw(); } private: T _shape; }; template <typename T, typename _ = void> struct is_shape : std::false_type {}; template <typename T> struct is_shape<T, typename std::enable_if< std::is_same<decltype(T().draw()), void>::value::type>: std::true_type {}; template <typename T> auto drawAgain(const T& shape) -> typename std::enable_if< is_shape<T>::value::type{ shape.draw(); } int main() { std::vector<std::unique_ptr<ShapeInterface>> shapes; shapes.emplace_back(new PolymorphicShape<SomeShape>(42)); shapes.emplace_back(new PolymorphicShape<OtherShape>(1, 2)); // Dynamic polymorphism: shapes[0]->draw(); // Prints: Drawing SomeShape shapes[1]->draw(); // Prints: Drawing OtherShape // Static polymorphism: drawAgain(SomeShape(123)); // Prints: Drawing SomeShape drawAgain(OtherShape(2, 4)); // Prints: Drawing OtherShape _getch(); return 0; }
Nov 14 2012
Here's the duck-typing design pattern they introduce in the video (in C++ again, sorry): #include <conio.h> #include <memory> #include <type_traits> #include <utility> #include <vector> class SomeShape { public: SomeShape() = default; explicit SomeShape(int volume) {} void draw() const { _cprintf("Drawing SomeShape\n"); } }; class OtherShape { public: OtherShape() = default; explicit OtherShape(int x, int y) {} void draw() const { _cprintf("Drawing OtherShape\n"); } }; class ShapeInterface { public: virtual void draw() const = 0; virtual ~ShapeInterface() {} }; template <typename T> class PolymorphicShape : public ShapeInterface { public: template <typename ...Args> PolymorphicShape(Args&&... args) : _shape (std::forward<Args>(args)...) {} void draw() const override { _shape.draw(); } private: T _shape; }; template <typename T, typename _ = void> struct is_shape : std::false_type {}; template <typename T> struct is_shape<T, typename std::enable_if< std::is_same<decltype(T().draw()), void>::value::type>: std::true_type {}; template <typename T> auto drawAgain(const T& shape) -> typename std::enable_if< is_shape<T>::value::type{ shape.draw(); } int main() { std::vector<std::unique_ptr<ShapeInterface>> shapes; shapes.emplace_back(new PolymorphicShape<SomeShape>(42)); shapes.emplace_back(new PolymorphicShape<OtherShape>(1, 2)); // Dynamic polymorphism: shapes[0]->draw(); // Prints: Drawing SomeShape shapes[1]->draw(); // Prints: Drawing OtherShape // Static polymorphism: drawAgain(SomeShape(123)); // Prints: Drawing SomeShape drawAgain(OtherShape(2, 4)); // Prints: Drawing OtherShape _getch(); return 0; }
Nov 14 2012
Here's the duck-typing design pattern they introduce in the video I posted (in C++ again, sorry): #include <conio.h> #include <memory> #include <type_traits> #include <utility> #include <vector> class SomeShape { public: SomeShape() = default; explicit SomeShape(int volume) {} void draw() const { _cprintf("Drawing SomeShape\n"); } }; class OtherShape { public: OtherShape() = default; explicit OtherShape(int x, int y) {} void draw() const { _cprintf("Drawing OtherShape\n"); } }; class ShapeInterface { public: virtual void draw() const = 0; virtual ~ShapeInterface() {} }; template <typename T> class PolymorphicShape : public ShapeInterface { public: template <typename ...Args> PolymorphicShape(Args&&... args) : _shape (std::forward<Args>(args)...) {} void draw() const override { _shape.draw(); } private: T _shape; }; template <typename T, typename _ = void> struct is_shape : std::false_type {}; template <typename T> struct is_shape<T, typename std::enable_if< std::is_same<decltype(T().draw()), void>::value::type>: std::true_type {}; template <typename T> auto drawAgain(const T& shape) -> typename std::enable_if< is_shape<T>::value::type{ shape.draw(); } int main() { std::vector<std::unique_ptr<ShapeInterface>> shapes; shapes.emplace_back(new PolymorphicShape<SomeShape>(42)); shapes.emplace_back(new PolymorphicShape<OtherShape>(1, 2)); // Dynamic polymorphism: shapes[0]->draw(); // Prints: Drawing SomeShape shapes[1]->draw(); // Prints: Drawing OtherShape // Static polymorphism: drawAgain(SomeShape(123)); // Prints: Drawing SomeShape drawAgain(OtherShape(2, 4)); // Prints: Drawing OtherShape _getch(); return 0; }
Nov 14 2012
Here's the duck-typing design pattern they introduce in the video I posted (in C++ again, sorry): #include <conio.h> #include <memory> #include <type_traits> #include <utility> #include <vector> class SomeShape { public: SomeShape() = default; explicit SomeShape(int volume) {} void draw() const { _cprintf("Drawing SomeShape\n"); } }; class OtherShape { public: OtherShape() = default; explicit OtherShape(int x, int y) {} void draw() const { _cprintf("Drawing OtherShape\n"); } }; class ShapeInterface { public: virtual void draw() const = 0; virtual ~ShapeInterface() {} }; template <typename T> class PolymorphicShape : public ShapeInterface { public: template <typename ...Args> PolymorphicShape(Args&&... args) : _shape (std::forward<Args>(args)...) {} void draw() const override { _shape.draw(); } private: T _shape; }; template <typename T, typename _ = void> struct is_shape : std::false_type {}; template <typename T> struct is_shape<T, typename std::enable_if< std::is_same<decltype(T().draw()), void>::value::type>: std::true_type {}; template <typename T> auto drawAgain(const T& shape) -> typename std::enable_if< is_shape<T>::value::type{ shape.draw(); } int main() { std::vector<std::unique_ptr<ShapeInterface>> shapes; shapes.emplace_back(new PolymorphicShape<SomeShape>(42)); shapes.emplace_back(new PolymorphicShape<OtherShape>(1, 2)); // Dynamic polymorphism: shapes[0]->draw(); // Prints: Drawing SomeShape shapes[1]->draw(); // Prints: Drawing OtherShape // Static polymorphism: drawAgain(SomeShape(123)); // Prints: Drawing SomeShape drawAgain(OtherShape(2, 4)); // Prints: Drawing OtherShape _getch(); return 0; }
Nov 14 2012
Here's the duck-typing design pattern they intrdouced in the video I posted (in C++ again, sorry): #include <conio.h> #include <memory> #include <type_traits> #include <utility> #include <vector> class SomeShape { public: SomeShape() = default; explicit SomeShape(int volume) {} void draw() const { _cprintf("Drawing SomeShape\n"); } }; class OtherShape { public: OtherShape() = default; explicit OtherShape(int x, int y) {} void draw() const { _cprintf("Drawing OtherShape\n"); } }; class ShapeInterface { public: virtual void draw() const = 0; virtual ~ShapeInterface() {} }; template <typename T> class PolymorphicShape : public ShapeInterface { public: template <typename ...Args> PolymorphicShape(Args&&... args) : _shape (std::forward<Args>(args)...) {} void draw() const override { _shape.draw(); } private: T _shape; }; template <typename T, typename _ = void> struct is_shape : std::false_type {}; template <typename T> struct is_shape<T, typename std::enable_if< std::is_same<decltype(T().draw()), void>::value::type>: std::true_type {}; template <typename T> auto drawAgain(const T& shape) -> typename std::enable_if< is_shape<T>::value::type{ shape.draw(); } int main() { std::vector<std::unique_ptr<ShapeInterface>> shapes; shapes.emplace_back(new PolymorphicShape<SomeShape>(42)); shapes.emplace_back(new PolymorphicShape<OtherShape>(1, 2)); // Dynamic polymorphism: shapes[0]->draw(); // Prints: Drawing SomeShape shapes[1]->draw(); // Prints: Drawing OtherShape // Static polymorphism: drawAgain(SomeShape(123)); // Prints: Drawing SomeShape drawAgain(OtherShape(2, 4)); // Prints: Drawing OtherShape _getch(); return 0; }
Nov 14 2012
Here's the duck-typing design pattern they intrdouced in the video I posted (in C++ again, sorry): #include <conio.h> #include <memory> #include <type_traits> #include <utility> #include <vector> class SomeShape { public: SomeShape() = default; explicit SomeShape(int volume) {} void draw() const { _cprintf("Drawing SomeShape\n"); } }; class OtherShape { public: OtherShape() = default; explicit OtherShape(int x, int y) {} void draw() const { _cprintf("Drawing OtherShape\n"); } }; class ShapeInterface { public: virtual void draw() const = 0; virtual ~ShapeInterface() {} }; template <typename T> class PolymorphicShape : public ShapeInterface { public: template <typename ...Args> PolymorphicShape(Args&&... args) : _shape (std::forward<Args>(args)...) {} void draw() const override { _shape.draw(); } private: T _shape; }; template <typename T, typename _ = void> struct is_shape : std::false_type {}; template <typename T> struct is_shape<T, typename std::enable_if< std::is_same<decltype(T().draw()), void>::value::type>: std::true_type {}; template <typename T> auto drawAgain(const T& shape) -> typename std::enable_if< is_shape<T>::value::type{ shape.draw(); } int main() { std::vector<std::unique_ptr<ShapeInterface>> shapes; shapes.emplace_back(new PolymorphicShape<SomeShape>(42)); shapes.emplace_back(new PolymorphicShape<OtherShape>(1, 2)); // Dynamic polymorphism: shapes[0]->draw(); // Prints: Drawing SomeShape shapes[1]->draw(); // Prints: Drawing OtherShape // Static polymorphism: drawAgain(SomeShape(123)); // Prints: Drawing SomeShape drawAgain(OtherShape(2, 4)); // Prints: Drawing OtherShape _getch(); return 0; }
Nov 14 2012