Search code examples
c++templatesc++17variadic-templatespointer-to-member

Invoke a method of templated derived class method from non-template base class pointer


I'm trying to implement a Property system in my project similar to Property system in Qt. We just started with some ideas and are in prototyping stage. Basically, what I understood from Qt is, client should be able to pass the get function, set function and property type through some macro in the .h file. So I tried to mimic the same.

Following is my sample code:

Abstract getter class. This type of getter class is a member in Property Class

class AbstractFunc
{
public:
  template < typename R > 
  R Invoke ()
  {
    return (this)->Invoke ();
  }
};

Get Function template: Return values can be T , T&, const T& , T* etc..

template < typename R, class T > class GetterFunction : public AbstractFunc
{
  typedef R (T::*GetterFunc) ();

public:
  GetterFunction (T * obj, GetterFunc func):m_Obj (obj), m_Func (func)
  {
  }

  R Invoke ()
  {
    return m_Obj->*(m_Func) ();
  }

public:
  T * m_Obj;
  GetterFunc m_Func;
};

Property Class:

class Property
{
public:
  Property (string name, AbstractFunc* getter):m_name (name), m_getter (getter)
  {

  }

  template < typename R > R GetValue ()
  {
    return m_getter->Invoke < R > ();
  }

private:
  string m_name;
  AbstractFunc* m_getter;
}; 

Some Window Class:

class Window
{
public:

};

Example window class

class CheckBox :public Window
{
public:
  int GetChecked ()
  {
    return m_checked;
  }
  void SetChecked (int nChecked)
  {
    m_checked = nChecked;
  }

  void AddProperty (string name)
  {
    m_prop = new Property (name, new GetterFunction< int, Checked >(this, &Checked::GetChecked));
  }

  int m_checked;
  Property *m_prop;
};

main function:

int main ()
{

  CheckBox cc;
  cc.AddProperty ("Hello");

  cout<<"value:"<< cc.m_prop->GetValue<int>();

  return 0;
}

PROBLEM: Getter function is remembered as AbstractFunc in Property Class. I want to call 'Invoke' on AbstractFunc* instance and it should invoke the member function and return correct return type. The above code throws error at AbstractFunc::Invoke.

see live


Solution

  • Your AbstractFunc isn't abstract at all: its Invoke isn't virtual. So even though GetterFunction also has a method named Invoke, that method doesn't actually override AbstractFunc::Invoke; it just hides it. When you try to call Invoke through the AbstractFunc*, it calls AbstractFunc::Invoke, which goes into infinite recursion and thus produces UB.

    I would follow @n.m.'s suggestion to make a class hierarchy like so:

    class AbstractFunc {
      // lock down construction
      AbstractFunc() = default;
    public:
      template<typename R>
      R Invoke();
      template<typename R>
      bool HasType() const noexcept;
      virtual ~AbstractFunc() = default; // need to have SOME virtual method so that we have runtime type info; also a virtual destructor is required anyway
    
      template<typename R>
      friend class TypedFunc;
    };
    template<typename R>
    struct TypedFunc : AbstractFunc { // the ONLY instances of AbstractFunc are also instances of specializations of TypedFunc
      virtual R InvokeTyped() = 0;
    };
    // one kind of TypedFunc applies a getter on an object
    template<typename R, typename T>
    struct GetterFunc : TypedFunc<R> {
      // you never see a GetterFunc in the interface anyway... don't see a need to hide these
      T *obj; // have you considered std::shared_ptr?
      R (T::*getter)();
      GetterFunc(T *obj, R (T::*getter)()) : obj(obj), getter(getter) { }
      R InvokeTyped() override { return (obj->*getter)(); }
    };
    template<typename R, typename T>
    std::unique_ptr<GetterFunc<R, T>> MakeGetterFunc(T *obj, R (T::*getter)()) {
      return std::make_unique<GetterFunc<R, T>>(obj, getter);
    }
    // another kind applies a functor, etc.
    template<typename R, typename F>
    struct FunctorFunc : TypedFunc<R> {
      F func;
      template<typename... G>
      FunctorFunc(G&&... args) : func(std::forward<G>(args)...) { }
      R InvokeTyped() override { return func(); }
    };
    

    This is already usable: if you have an AbstractFunc* or an AbstractFunc&, you can dynamic_cast it down to a TypedFunc of the expected type (e.g. TypedFunc<int>). If that succeeds (you get a nonnull pointer or there is no std::bad_cast exception), then you just call InvokeTyped without having to know what kind of GetterFunc/FunctorFunc/whatever you are actually dealing with. The functions Invoke and HasType declared in AbstractFunc are just sugar to help do this.

    template<typename R>
    bool AbstractFunc::HasType() const noexcept {
      return dynamic_cast<TypedFunc<R> const*>(this);
    }
    template<typename R>
    R AbstractFunc::Invoke() {
      return dynamic_cast<TypedFunc<R>&>(*this).InvokeTyped();
      // throws std::bad_cast if cast fails
    }
    

    Done.

    class Property {
      std::string name;
      std::unique_ptr<AbstractFunc> getter;
    public:
      Property(std::string name, std::unique_ptr<AbstractFunc> getter) : name(std::move(name)), getter(std::move(getter)) { }
      template<typename R>
      bool HasType() const noexcept { return getter->HasType<R>(); }
      template<typename R>
      R GetValue() const { return getter->Invoke<R>(); }
      std::string const &GetName() const noexcept { return name; }
    };
    struct Window {
      virtual ~Window() = default;
      // doesn't really make sense to add/remove these from outside...
      virtual std::vector<Property> GetProperties() = 0;
    };
    class CheckBox : public Window {
      int checked = 0;
    public:
      int GetChecked() /*const*/ noexcept { return checked; }
      void SetChecked(int checked) noexcept { this->checked = checked; }
      std::vector<Property> GetProperties() override {
        std::vector<Property> ret;
        ret.emplace_back("Boxes Checked", MakeGetterFunc(this, &CheckBox::GetChecked));
        return ret;
      }
    };
    
    int main() {
      CheckBox cb;
      cb.SetChecked(5);
      for(auto const &prop : cb.GetProperties()) std::cout << prop.GetName() << ": " << prop.GetValue<int>() << "\n";
    }
    

    You could then add e.g. a virtual std::type_info const& GetType() const or similar to AbstractFunc if you want to be able to directly get at the type, etc.