463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122840

Listener Pattern via Templates, how to use templated class without specifying template parameter? c++

I want to implement a Reciever/Publisher class via templates. I am not sure whether templates is the best for this. However, what I got so far is this:

#include <vector>
template <typename InType,typename OutType> class Pipe {
    public:
        void addListener(Pipe<OutType,???> l){
            listener.push_back(l);
        }
        virtual OutType process(InType)=0;
        void Fill(InType value){
            OutType outValue = process(value);
            for (int i=0;i<listener.size();i++){
                listener[i]->Fill(outValue);
            }
        }
    private:
        vector<Pipe<OutType,???>*> listener;
};

A Pipe should allow listener with different OutTypes. Actually, it does not need to know the OutType of its listeners, as the process() is called by the listeners. However, I have to put an template parameter. Is there something wrong with my approach? Or is there a way to avoid specifying the OutType of the listeners?

Upvotes: 2

Views: 396

Answers (2)

Anton Savin
Anton Savin

Reputation: 41321

You can make Pipe<InType, OutType> inherit a common base class depending only on InType:

template <typename InType>
class PipeBase {
public:
    virtual ~PipeBase() = default;
    virtual void Fill(InType value) = 0;
};

template <typename InType, typename OutType>
class Pipe : public PipeBase<InType> {
public:
    void addListener(PipeBase<OutType>* l){
        listener.push_back(l);
    }

    void Fill(InType value) { ... }

private:
    std::vector<PipeBase<OutType>*> listener;
};

Demo

Upvotes: 3

Casey
Casey

Reputation: 42574

The source of your confusion is conflating the concept of Listener, something that is notified of an event, with Pipe - something that is notified of an event, performs some processing, and then notifies a set of Listeners of the results of that processing. In this model, a Pipe is-a Listener, but a Listener is not necessarily a Pipe.

I'd suggest that you (1) allow any callable object that can accept OutType to be a listener, (2) have Pipe accept input via operator() so it is a valid listener for InType. You can then use std::function<void(OutType)> to erase the type of listeners, and while you're at it, use std::function<OutType(Intype)> to erase the type of the transform function:

template <typename InType, typename OutType>
class Pipe {
public:
  Pipe(std::function<OutType(InType)> transform) :
    transform_(std::move(transform)) {}

  // For lvalues, return reference to *this for chaining.
  Pipe& addListener(std::function<void(OutType)> f) & {
    listeners_.push_back(std::move(f));
    return *this;
  }

  // For rvalues, return *this by value for chaining.
  Pipe addListener(std::function<void(OutType)> f) && {
    listeners_.push_back(std::move(f));
    return std::move(*this);
  }

  void operator() (InType value) {
    const auto outValue = transform_(std::move(value));
    for (auto& listener : listeners_) {
      listener(outValue);
    }
  }

private:
  std::function<OutType(InType)> transform_;
  std::vector<std::function<void(OutType)>> listeners_;
};

DEMO

Upvotes: 1

Related Questions