0

I'm struggling with a problem regarding the linking between two derived classes from a template one. Let's say that we have a template base class Param_B which take as parameter one of it's children (Param_S or Param_M). The template parameter of the class Param_B is used further for definition of other local variables( _parent and a callback function _func). For me is necessary to have the Param_S or Param_M for dynamically call of the function.

The problem is that after I create the pointers to the children's and add timers and functions to them I want to have a _link Variable which will enable to exchange information between both children types. E.g. from the Param_S I want to change the timeout of a Param_M timer by using the _link variable. I

#include <stdio.h>
#include <memory>
#include <vector>

class Param_S;
class Param_M;

template <class T>
class Functionn{
    Functionn(){};
    ~Functionn(){};
private:
    std::function<void(std::shared_ptr<T>)> _callback;
};


template <class T>
class Timer
{
public:
    Timer(){ timeout = 0; };
    ~Timer(){};

    void setTimeout(const int time){    timeout = time; };
    std::weak_ptr<Functionn<T>>& getFunction(){ return _func; };
    std::weak_ptr<T> getParent(){   return _parent;};

private:
    int timeout;
    std::weak_ptr<T> _parent;
    std::weak_ptr<Functionn<T>> _func;
};



template <class T>
class Param_B {
public:
    Param_B(){};
    ~Param_B(){};

    std::vector<std::shared_ptr<Timer<T>>>& getTimers(){
        return _timers;
    }
    void addTimer(std::shared_ptr<Timer<T>> _t){
        _timers.push_back(std::move(_t));
    }
    std::weak_ptr<Param_B> getLink(){
            return _link;
    }
    void setLink(std::weak_ptr<Param_B> link){
            _link = link;
    }

private:
    std::weak_ptr<Param_B> _link;
    std::vector<std::shared_ptr<Timer<T>>> _timers;
};



class Param_S : public Param_B<Param_S>
{
public:
    Param_S(){ paramS = -2; };
    ~Param_S(){};

    void setValue(){
        paramS = 33;
    }

    int getValue(){ return paramS;};

private:
    float paramS;
};


class Param_M : public Param_B<Param_M>
{
public:
    Param_M(){ parammM = -4; };
    ~Param_M(){};
    int getValue(){ return parammM; };

private:
    int parammM;

};


class sys{
public:

    std::vector<std::shared_ptr<Param_S>> getParams(){
        return _params;
    }

private:
    std::vector<std::shared_ptr<Param_S>> _params;
};

class module{
public:
    std::vector<std::shared_ptr<Param_M>> getParams(){
        return _params;
    }
private:
    std::vector<std::shared_ptr<Param_M>> _params;
};

void main(){

    std::shared_ptr<sys> _sys = std::make_shared<sys>();
    _sys->getParams().push_back(std::make_shared<Param_S>());
    _sys->getParams()[0]->addTimer(std::make_shared<Timer<Param_S>>());


    std::shared_ptr<module> _mod = std::make_shared<module>();
    _mod->getParams().push_back(std::make_shared<Param_M>());
    _mod->getParams()[0]->addTimer(std::make_shared<Timer<Param_M>>());


    _sys->getParams()[0]->setLink(_mod->getParams()[0]); // this not work , the conversion failed 
}

Thank you for your help.

  • The two children are not polymorphic. For that you'd need [template covariance](https://stackoverflow.com/questions/2203388/c-templates-polymorphism) and it generally creates more issues than it solves. This is not the exact situation you are facing, but the issue is the same – Lala5th Aug 27 '21 at 10:01
  • 2
    `getParams()` shoud return a reference in `sys` and `module` – m88 Aug 27 '21 at 10:05
  • What do you want to be able to do with `getLink()`? – Caleth Aug 27 '21 at 11:25
  • @Caleth be calling `getLink()` I am then able to get the timers of the linked parameter ( param_M or Param_S) and change the timer timeout for example. Another point is to call the function attached to the timer. – Durneata Dan Aug 27 '21 at 11:38

2 Answers2

1

You need to have a non-template base for Param_B. It can't involve anything related to the template parameter, but you could have virtual functions here

class Param_B_Base {
public:
    virtual ~Param_B_Base() = default;

    std::weak_ptr<Param_B_Base> getLink(){
            return _link;
    }
    void setLink(std::weak_ptr<Param_B_Base> link){
            _link = link;
    }

    // an example of possible behaviour
    virtual void setTimeouts(const int time) = 0;

    // another example of possible behaviour
    virtual void invokeHandlers() = 0;

private:
    std::weak_ptr<Param_B_Base> _link;
};

template <class T>
class Functionn{
    void operator()(std::shared_ptr<T> ptr) { _callback(ptr); }
private:
    std::function<void(std::shared_ptr<T>)> _callback;
};

template <class T>
class Timer
{
public:

    void setTimeout(const int time){    timeout = time; };
    void invoke() { if (auto func = _func.lock()) { if (auto parent = parent.lock() { (*func)(parent); } } }

private:
    int timeout = 0;
    std::weak_ptr<T> _parent;
    std::weak_ptr<Functionn<T>> _func;
};

Then you can have a template for subclasses

template <class T>
class Param_B : public Param_B_Base {
public:

    std::vector<std::shared_ptr<Timer<T>>>& getTimers(){
        return _timers;
    }
    void addTimer(std::shared_ptr<Timer<T>> _t){
        _timers.push_back(std::move(_t));
    }

    // The implementation of the virtual methods can know about T
    void setTimeouts(const int timeout) override {
        for (std::shared_ptr<Timer<T>> timer : _timers) {
            timer->setTimeout(timeout);
        }
    }

    void invokeHandlers() override {
        for (std::shared_ptr<Timer<T>> timer : _timers) {
            timer->invoke();
        }
    }
private:
    std::vector<std::shared_ptr<Timer<T>>> _timers;
};
Caleth
  • 52,200
  • 2
  • 44
  • 75
0

I would suggest making the link between the two derived classes type-agnostic (i.e. make the link a std::function rather than a pointer to an instance of the other type.

If you would like to keep it as a pointer, your problem is in your use of Param_B within the Param_B template class.

void setLink(std::weak_ptr<Param_B> link)

In the above line, Param_B will refer to the Param_B<Param_M> when the method is called on an instance of Param_B<Param_M>, and it will refer to Param_B<Param_S> when the method is called on an instance of Param_B<Param_S>.

What you need to do is add a second template parameter to Param_B to tell it what the linked derived type is.

template <class T, class L>
class Param_B {
public:
    Param_B(){};
    ~Param_B(){};

    std::vector<std::shared_ptr<Timer<T>>>& getTimers(){
        return _timers;
    }
    void addTimer(std::shared_ptr<Timer<T>> _t){
        _timers.push_back(std::move(_t));
    }
    std::weak_ptr<Param_B> getLink(){
            return _link;
    }
    void setLink(std::weak_ptr<Param_B<L, T>> link){
            _link = link;
    }

private:
    std::weak_ptr<Param_B<L, T>> _link;
    std::vector<std::shared_ptr<Timer<T>>> _timers;
};

Then you'll need a forward declaration of Param_M so it can be a template parameter for Param_S.

class Param_M;

class Param_S : public Param_B<Param_S, Param_M>
{
...
};
class Param_M : public Param_B<Param_M, Param_S>
{
...
};

Edit:

If you know how the derived types will interact, don't use functions as links; see the answer from @Caleth.

jisrael18
  • 719
  • 3
  • 10
  • This assumes the link always goes to an exact mirror type, not any instantiation of `Param_B` – Caleth Aug 27 '21 at 11:24
  • Also, the `getParams` methods need to return a reference to the vector. – jisrael18 Aug 27 '21 at 11:25
  • Thank you for this solution. It works indeed, but the my idea was to have a _link parameter which could be Param_M or Param_S type and to be more flexible. – Durneata Dan Aug 27 '21 at 11:28
  • @Caleth Yes, you are correct. That's why my first suggestion was to use functions for the link. Though, depending on the intended use, it may require multiple functions to be stored as links (e.g. a setter for multiple members). – jisrael18 Aug 27 '21 at 11:28
  • @durneata-dan Unfortunately, I don't think there's a way to do this using a link member that's a pointer to another instantiation of `Param_B`. There's no such thing as a "generic" `Param_B`. If you could answer @Caleth's question about how you intend to use the link, it would help users suggest alternate ways to set up the links. – jisrael18 Aug 27 '21 at 11:38
  • @durneata-dan Added an example of using a function for the link. – jisrael18 Aug 27 '21 at 11:48