Jump to page: 1 2 3
Thread overview
Getting rid of dynamic polymorphism and classes
Nov 08, 2012
Tommi
Nov 08, 2012
Jakob Ovrum
Nov 08, 2012
Tommi
Nov 08, 2012
F i L
Nov 09, 2012
Ziad Hatahet
Nov 09, 2012
Era Scarecrow
Nov 09, 2012
Iain Buclaw
Nov 09, 2012
Era Scarecrow
Nov 08, 2012
Max Klyga
Nov 08, 2012
Tommi
Nov 10, 2012
Marco Leise
Nov 10, 2012
Marco Leise
Nov 10, 2012
Tommi
Nov 10, 2012
F i L
Nov 09, 2012
Era Scarecrow
Nov 09, 2012
Eldar Insafutdinov
Nov 11, 2012
gnzlbg
Nov 12, 2012
Tommi
Nov 12, 2012
Tommi
Nov 15, 2012
Tommi
Nov 12, 2012
Tommi
Nov 15, 2012
Tommi
Nov 15, 2012
Tommi
Nov 15, 2012
Tommi
Nov 15, 2012
Tommi
Nov 15, 2012
Tommi
Nov 15, 2012
Tommi
November 08, 2012
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

November 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.


November 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.




November 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()
    }
November 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 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.

November 08, 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.

November 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.
November 09, 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...)
November 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.
November 09, 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


« First   ‹ Prev
1 2 3