0

I want to create a "pass by value" layout manager. My idea is to have the layout manager initialized like this:

int main() {
    // Tree hierarchy is good!
    auto lmgr = vertical_layout_pane("grid")
        << ( horizontal_layout_pane("row 1") << layout_pane("col 1.1") )
        << ( horizontal_layout_pane("row 2") << layout_pane("col 2.1") << layout_pane("col 2.2") );

    // Apply layout.
    lmgr.apply({ 0,0,100,100 });

    return 0;
}

To do this I create class hierarchy, like this:

#include <vector>
#include <iostream>

struct rct {
public:
    int x, y, w, h;
};

struct has_name {
public:
    has_name(std::string name) : name_(name){}
protected:
    std::string name_;
};

struct layout_pane : public has_name {
public:
    layout_pane(std::string name) : has_name(name) {}
    // Apply layout to child panes.
    virtual void apply(rct r) {
        std::cout << "layout_pane("
            << r.x << "," << r.y << ","
            << r.w << "," << r.h
            << ")" << std::endl;
        for (layout_pane& p : children_)
            p.apply(r); // Default layout maximizes children.
    }

    // Here's our overloaded operator<<
    layout_pane& operator<<(const layout_pane& child)
    {
        add(child);
        return *this;
    }
protected:
    std::vector<layout_pane> children_; // Child panes.
    void add(const layout_pane& child) {
        children_.push_back(child);
    }
};

// Structure children vertically,
struct vertical_layout_pane : public layout_pane {
public:
    vertical_layout_pane(std::string name) : layout_pane(name) {}
    // Apply layout to child panes.
    void apply(rct r) override {
        std::cout << "vertical_layout_pane("
            << r.x << "," << r.y << ","
            << r.w << "," << r.h
            << ")" << std::endl;
        auto n = children_.size();
        if (n > 0) {
            int y = 0, y_step = r.h / n;
            for (layout_pane& p : children_) {
                p.apply({ r.x,y,r.w,y_step }); // Default layout maximizes children.
                y += y_step;
            }
        }
    }
};

// Structure children horizontally.
struct horizontal_layout_pane : public layout_pane {
public:
    horizontal_layout_pane(std::string name) : layout_pane(name) {}
    // Apply layout to child panes.
    void apply(rct r) override {
        std::cout << "horizontal_layout_pane("
            << r.x << "," << r.y << ","
            << r.w << "," << r.h
            << ")" << std::endl;
        auto n = children_.size();
        if (n > 0) {
            int x = 0, x_step = r.w / n;
            for (layout_pane& p : children_) {
                p.apply({ x,r.y,x_step,r.h }); // Default layout maximizes children.
                x += x_step;
            }
        }
    }
};

When I debug the code I can see the correct tree being created. However, when the apply function is called - it always calls the base class. The output is:

layout_pane(0,0,100,100)
layout_pane(0,0,100,100)
layout_pane(0,0,100,100)
layout_pane(0,0,100,100)
layout_pane(0,0,100,100)
layout_pane(0,0,100,100)

I'm guessing somehow the cast strips member of its original type.

Tomaz Stih
  • 529
  • 3
  • 10
  • 1
    Check this post for a very in depth answer about object slicing: https://stackoverflow.com/questions/274626/what-is-object-slicing – MorningDewd Oct 29 '20 at 20:52

1 Answers1

0

After days of studying move semantics, I think I solved it. The idea is to

  1. use the template to create different << operators for all derivates,
  2. have temporaries passed as rvalue references, and other values as lvalue references.
  3. use forwarding to create references from everything.
  4. return correct reference (cast to template type),
  5. move forwarded reference to smart pointer, and
  6. store it to the vector.
#include <vector>
#include <memory>
#include <iostream>

class layout {
public:
    virtual std::string me() { return "layout";}
    virtual void display(int spaces=0) {
        std::cout << std::string( spaces, ' ' ) << me() << std::endl;
        for(auto &p : children_) p->display(spaces+3);
    }

    template<class T>
    T & operator << (T && child) {
        return std::forward<T&>(add(child));
    }

    template<class T>
    T & operator << (T & child) {
        return std::forward<T&>(add(child));
    }

private:
    
    template<class T>
    T & add(T& child) {
        std::unique_ptr<T> heap_chld=std::make_unique<T>();
        *heap_chld=std::move(child); // Type sliced, but to correct type.
        children_.push_back(std::unique_ptr<T>(std::move(heap_chld)));
        return (T&)(*this);
    }

    std::vector<std::unique_ptr<layout>> children_;
};

class vlayout : public layout {
public:
    virtual std::string me() { return "vlayout";}
};

class hlayout : public layout {
public:
    virtual std::string me() { return "hlayout";}
};

int main() {
    auto l = layout();
    auto hl = hlayout(); 
    l << vlayout() 
        << (
            hlayout()
            << hlayout() // Temporary.
            << hl // Reference.
        )
        << hlayout();
    l.display();
    return 0;
}
Tomaz Stih
  • 529
  • 3
  • 10