www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Getting rid of dynamic polymorphism and classes

reply "Tommi" <tommitissari hotmail.com> writes:
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
next sibling parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
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
prev sibling next sibling parent reply "DypthroposTheImposter" <mcbracket gmail.com> writes:
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
parent "Tommi" <tommitissari hotmail.com> writes:
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 
 usage
But 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
prev sibling next sibling parent reply "F i L" <witte2008 gmail.com> writes:
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
parent reply Ziad Hatahet <hatahet gmail.com> writes:
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
parent reply "Era Scarecrow" <rtcvb32 yahoo.com> writes:
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
parent reply Iain Buclaw <ibuclaw ubuntu.com> writes:
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:
 Method dispatch is still done at runtime though.
If opDispatch is small&simple can/does it get inlined to the callers?
Potentially yes, but only with optimisation turned on. ;-) -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';
Nov 09 2012
parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
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:
  If opDispatch is small&simple can/does it get inlined to the 
 callers?
Potentially yes, but only with optimization turned on. ;-)
I thought so, and hope so :)
Nov 09 2012
prev sibling next sibling parent reply Max Klyga <email domain.com> writes:
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
parent reply "Tommi" <tommitissari hotmail.com> writes:
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
parent reply Marco Leise <Marco.Leise gmx.de> writes:
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:
 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.
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. -- Marco
Nov 10 2012
next sibling parent Marco Leise <Marco.Leise gmx.de> writes:
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
prev sibling parent reply "Tommi" <tommitissari hotmail.com> writes:
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
parent "F i L" <witte2008 gmail.com> writes:
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
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
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
prev sibling next sibling parent "Eldar Insafutdinov" <e.insafutdinov gmail.com> writes:
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
prev sibling parent reply "gnzlbg" <gonzalo gadeschi.es> writes:
How would you create a vector of canvas and iterate over them?
Nov 11 2012
parent reply "Tommi" <tommitissari hotmail.com> writes:
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
next sibling parent reply "Tommi" <tommitissari hotmail.com> writes:
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
parent "Tommi" <tommitissari hotmail.com> writes:
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
prev sibling parent reply "Tommi" <tommitissari hotmail.com> writes:
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
next sibling parent "Tommi" <tommitissari hotmail.com> writes:
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
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
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
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
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
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
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
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
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
prev sibling parent "Tommi" <tommitissari hotmail.com> writes:
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