0

I'm writing some arduino libraries and would like to improve readability / add some syntactic suggar.

What I would like to do is create objects on the heap in a way that would look like:

Panel panel( 
    Button( 1 ).on( Click( clickfunc ) ),
    Button( 2 ).on( Hold( holdfunc, 1000 ) )
);

(Button, Click, Hold are all classes and internally managed via linked lists (so they aren't constant.))

I tried writing it this way but I stumbled over problems with references to temporaries.

Currently I can use:

Button button1( 1 ), button2( 2 );
Click theClick( clickFunction );
Hold theHold( holdFunction, 1000 );
Panel( button1.on( theClick ), button2.on( theHold ) );

but this is not nearly as readable as the above and tends to be error-prone because you have to stay alert and don't put e.g. theClick on another button which would break the linked list.

Some heavily shortened excerpts from the classes like they are now.

class Button {
    Handler *_first;
    Button( int no ){...}
    Button & on( Handler &handler ){
        handler._next = _first;
        _first = &handler;
        return *this;
    }
    void handle( int oldValue, int newValue ) {
        Handler *handler;
        for( handler = _first; handler; handler = handler->_next ){
            handler->handle( oldValue, newValue );
        }
    }
}
class Handler {
    Handler *_next;
    virtual void handle( int oldValue, int newValue ) = 0;
    ...
}
class Click : public Handler {
    ...
}
class Hold : public Handler {
    ...
}

Note that this doesn't necessarily needs to stay this way. The goal is to provide a library where its user doesn't need to know to much about its inner working but has a simple/clean interface.

Kara
  • 6,115
  • 16
  • 50
  • 57
Scheintod
  • 7,953
  • 9
  • 42
  • 61
  • 1
    Note: Objects on the heap are always anonymous, since they never have a name: they are never variables. – Kerrek SB Jan 23 '17 at 17:38
  • Can you add the exact errors you were getting when you tried to one line it? – George Jan 23 '17 at 17:39
  • @drescherjm: maybe. But why do you think it's not on the heap? (Or am I using the word "Heap" wrongly in cpp context?)) – Scheintod Jan 23 '17 at 17:39
  • @George: I had a seperate question on for that: https://stackoverflow.com/questions/41811375/non-const-anonymous-reference-in-constructor. The error looks like: "error: no matching function for call to ‘X::X(A)’ / no known conversion for argument 1 from ‘A’ to ‘A&" – Scheintod Jan 23 '17 at 17:42
  • Actually Scheintod, C++ does not formally acknowledge the existence of a heap. It is merely a pool of dynamic storage. I'm pretty sure Arduino implements dynamic memory with the heap concept, though. – user4581301 Jan 23 '17 at 17:43
  • Where do the linked lists live and what do the signatures for Click, Hold and Panel look like? – George Jan 23 '17 at 17:54

1 Answers1

1

If you have problem with dangling references with the code above, I suspect you are making a linked list that create references (or pointer) that points to those element on the stack.

I suspect also that your signature looks like this:

Button& on(const Event& event) { /* ... */ }

To help you with your problem, I suggest to change the signature of your on function to something like this:

template<typename EventType>
Button& on(EventType&& event) {

}

That way, you can actually forward the object into the heap, and use some form of type easure to put it into your linked list:

struct Handler {
    virtual void handle(int oldValue, int newValue) = 0;

    // Defaulted virtual destructor
    virtual ~Handler() = default;
};

template<typename T>
struct HandlerImpl : Handler {
    // constructors
    HandlerImpl(T h) : handler{std::forward<T>(h)} {}

    void handle(int oldValue, int newValue) {
        handler.handle(oldValue, newValue);
    }

    // We use the compiler generated destructor

private:
    remove_rvalue_reference_t<T> handler;
};

template<typename HandlerType>
Button& on(HandlerType&& event) {
    // See the code example below
}

What changes in the rest of your code?

Well, now both syntax you posted are supported. The first syntax will move and hold the variable. The second syntax will only hold references to the events and will assume that the lifetime of the event are equal or bigger of those of the button.

Also, Click and Hold don't need to extend any class nor need virtual function or virtual destructors.

If you don't want the second syntax to hold references and use copy instead, replace remove_rvalue_reference_t by std::remove_reference_t.

This pattern I showed you can be applied for Button, and for any widget type you want.


Here's how remove_rvalue_reference_t is implemented:

template<typename T> struct remove_rvalue_reference { using type = T; };
template<typename T> struct remove_rvalue_reference<T&&> { using type = T; };
template<typename T> using remove_rvalue_reference_t = typename remove_rvalue_reference<T>::type;

Since you have posted an example of your code, I can now help you transform it so it can work with the code above.

First, liked list are slow, and hand rolled liked list are worse. I strongly suggest you to use std::vector. Secondly, std::unique_ptr is the preferred way to hold owning pointers. So, just by following this and the steps mentioned above, your code should look like this:

struct Button {
    std::vector<std::unique_ptr<Handler>> _handlers;

    Button(int no) { /* ... */ }

    // This function will work for any type that
    // happen to have an `handle` function.
    template<typename H> // <--- H is the handler type
    Button& on(H&& handler) { // H&& in this case means forwarding reference.
        // We add (emplace) a new HandlerImpl, allocated on the heap using `std::make_unique`
        _handlers.emplace_back(
            std::make_unique<HandlerImpl<H>>(std::forward<H>(handler))
        );

        return *this;
    }

    void handle(int oldValue, int newValue) {
        // We use a range for loop here to iterate on the vector
        for (auto&& handler : _handlers) {
            handler->handle(oldValue, newValue);
        }
    }
};

// We do not extends anything
struct Click {
    // Notice that the function is not virtual
    void handle(int oldVal, int newVal) {/* ... */}
};

struct Hold {
    void handle(int oldVal, int newVal) {/* ... */}
};

Here's a live example at Coliru

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • Thanks for the answer! But holy shit (!) now I first have to dig deeply into all kind of c++ stuff that I have no clue off. (But shame on me, I asked for magic ;) – Scheintod Jan 23 '17 at 18:23
  • @Scheintod I added a more concrete example that works with your code. – Guillaume Racicot Jan 23 '17 at 18:42
  • Thanks again. Still trying to figure stuff out :) I think on arduino I don't have std::vector. Hm. std::unique_ptr it doesn't like either ... – Scheintod Jan 23 '17 at 18:47
  • @Scheintod To activate C++11 (or even better, 14), you need to tell your compiler to enable it with the `-std=c++14`. GCC 6 and MSVC enables it by default in the latest version. Clang still need that flag. BTW, I also added a live example. As you can see, the flag is there. – Guillaume Racicot Jan 23 '17 at 18:53
  • Cool. I'm pretty impressed. – Scheintod Jan 23 '17 at 18:56