3

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?

Tomaz Stih
  • 529
  • 3
  • 10
  • 2
    no you can't do `new app_wnd()` because `app_wnd` is a template and since the example does not work, you have other problems than the one that you are having now (and also the `class app_wnd : public wnd` won't compile) – Alberto Sinigaglia Jul 20 '20 at 12:47
  • Sorry, I copy pasted older version of code. I updated it now. The problem is still there. – Tomaz Stih Jul 20 '20 at 12:52
  • CRTP requires templates all the way down because everybody has to be able to convert back to the top class. `template class app_wnd : public wnd { ... }; class main_wnd : public app_wnd { ... };` – Raymond Chen Jul 20 '20 at 13:00
  • @Berto99 `class app_wnd : public wnd` why not? `app_wnd` is not complete, but it need not be – 463035818_is_not_an_ai Jul 20 '20 at 13:00
  • @idclev463035818 you are watching a different code that i was previusly watching, he edited the quesiton – Alberto Sinigaglia Jul 20 '20 at 13:02
  • Do I understand it correctly, that CRTP requires knowing which class is "the final class" in derivation chain? So if I have button and I want to create custom_button from it, then button can't be final class as it needs a template. to pass through? – Tomaz Stih Jul 20 '20 at 13:16

2 Answers2

1

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);
Tomaz Stih
  • 529
  • 3
  • 10
0

you can do something like this:

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_;
};
template<class Derivate>
class app_wnd : public wnd<Derivate> {
public:
    app_wnd* show() { std::cout << wnd<app_wnd<Derivate>>::text_ << " " << wnd<app_wnd<Derivate>>::id_; return (Derivate*) this; }
};
class main_wnd : public app_wnd<main_wnd> {
public:
    main_wnd* set_app_instance(int instance) { instance_ = instance; return (main_wnd *) this; }
protected :
    int instance_;
};
int main(){
    (new main_wnd())->set_id(0)->set_app_instance(10);
}

(inspired by CRTP).

Hoooowever you won't be able to instantiate no more app_wnd

Alberto Sinigaglia
  • 12,097
  • 2
  • 20
  • 48
  • This would work as long as we know what the final class is. However, if we have, and that is quite possible, a button class - it may be final or one may derive custom_button from it. – Tomaz Stih Jul 20 '20 at 13:08