Search code examples
c++fluent

C++ fluent interface


I'm implementing a fluent interface for my C++ GUI library. The basic concept is for every function to return a pointer to template.

Here are two classes.

template <class T>
class wnd {
public:
    T* set_id(int id) { id_ = id; return (T*)this; }
    T* set_text(std::string text) { text_ = text; return (T*)this; }
protected:
    int id_;
    std::string text_;
};
    
class app_wnd : public wnd<app_wnd> {
public:
    app_wnd* show() { std::cout << text_ << " " << id_; return (app_wnd*) this; }
};

This works great. When using app_wnd the children functions return correct T* i.e. app_wnd*. Hence I can do (new app_wnd())->set_id(0)->set_text("hello world")->show();

Problems start when I do another derivation.

class main_wnd : public app_wnd {
public:
    main_wnd* set_app_instance(int instance) { instance_ = instance; return (main_wnd *) this; }
protected : 
    int instance_;
};

This class does not know about the underlying template and as soon as I call set_id() it returns the app_wnd class, losing methods of main_wnd i.e. (new main_wnd())->set_id(0)->set_app_instance(10); will fail because set_id function will return app_wnd* and not main_wnd*.

Is there a clean, nice, non-complex solution to that?


Solution

  • I feel a bit younger now, because I found an answer on Stack Exchange. What is needed is a modified CRTP using default template arguments. Here's the class code.

    template <typename Q, typename T>
    struct fluent_type {
        typedef Q type;
    };
    
    template <typename T>
    struct fluent_type<void, T> {
        typedef T type;
    };
    
    template <typename T>
    class wnd {
    public:
        typedef typename fluent_type<T, wnd<void>>::type ftype;
        ftype* set_id(int id) { id_ = id; return static_cast<ftype*>(this); }
        ftype* set_text(std::string text) { text_ = text; return static_cast<ftype*>(this); }
    protected:
        int id_;
        std::string text_;
    };
    
    template <typename T =  void>
    class app_wnd : public wnd<app_wnd<T>> {
    public:
        typedef typename fluent_type<T, wnd<app_wnd<T> > >::type ftype;
        ftype* show() { std::cout << text_ << " " << id_; return static_cast<ftype*>(this);
        }
    };
    
    template <typename T = void>
    class main_wnd : public app_wnd<main_wnd<T>> {
    public:
        typedef typename fluent_type<T, app_wnd<main_wnd<T> > >::type ftype;
        ftype* set_app_instance(int instance) { instance_ = instance; return static_cast<ftype*>(this); }
    protected : 
        int instance_;
    };
    

    And here are the sample calls.

    auto aw = (new app_wnd<>())
        ->set_id(0)
        ->set_text("hello app_wnd")
        ->show();
    
    auto mw = (new main_wnd<>())
        ->set_id(0)
        ->set_text("hello main_wnd")
        ->show()
        ->set_app_instance(123);