1

I have a variadic template class implementing 4 methods defined in a base class iprocessing_component_manager. The 4 methods manage the life cycle of a component, such as initialization, preparation and recycling and/or disposal. The fully implemented interface tuple_processing_component_manager takes component types as a variadic argument.

The component manager is a member of a controller to execute the sequence of components.

The following code is a base skeleton for my controller and working fine so far. It has one possible component manager implementation, here I just show a simple implementation to illustrate:

#include "utilities.h"
#include <cstdlib>
#include <iostream>
#include <tuple>

// https://stackoverflow.com/questions/42255534 (Yakk)
namespace notstd {
    template<class T> struct tag_t { constexpr tag_t() {}; using type=T; };
    template<class T> constexpr tag_t<T> tag{};
    template<class Tag> using type_t = typename Tag::type;

    template<class...Ts, class F>
    void for_each_type(F&& f) {
        using discard=int[];
        (void)discard{ 0,(void(
            f( tag<Ts> )
        ),0)...};
    }
}

// A component
class icomponent {
public:
    virtual std::string id() = 0; 
    virtual ~icomponent() = default;

    virtual void init() = 0;
    virtual void dispose() = 0;
};

class component_base : public icomponent
{   
public:
    virtual ~component_base() = default;

    virtual void init() 
    {
        // ... init context
    }

    virtual void dispose()
    {
        // ...
    }

    // ... more
};

// Sample components
class component_a : public component_base { 
public:    
    virtual std::string id() override { return "component a"; } 
};
class component_b : public component_base { 
public:    
    virtual std::string id() override { return "component b"; } 
};
class component_c : public component_base { 
public:    
    virtual std::string id() override { return "component c"; } 
};

// Interface component manager
class iprocessing_component_manager {
public:
    virtual ~iprocessing_component_manager() = default;

    virtual void init() = 0;
    virtual icomponent* prepare() = 0;
    virtual void recycle(icomponent* p) = 0;
    virtual void dispose() = 0;
};

// Implementation component manager
template<typename T>
class  type_processing_component_manager
    : public iprocessing_component_manager {
public:
    virtual ~type_processing_component_manager() = default;

    virtual T* prepare() override 
    {
        // Default create T or fetch from a object pool, etc ...
        return new T;
    }
};

// Implementation virt. methods component mgr
template<typename ... Ts>
class tuple_processing_component_manager 
    : public type_processing_component_manager<Ts>... {
public:
    virtual ~tuple_processing_component_manager() = default;

    virtual void init() override
    {

    }

    template<typename T>
    T* prepare() 
    {
        return type_processing_component_manager<T>::prepare();
    }

    virtual void recycle(icomponent* p) override
    {            
        // Delete pointer or return to an object pool, etc
        delete p;
    }

    virtual void dispose() override
    {

    }
};

// The controller
template <typename ...Ts> 
class controller {
    std::unique_ptr<tuple_processing_component_manager<Ts...>> m_component_manager;
//    iprocessing_component_manager* m_component_manager;

public:
    controller()
    : m_component_manager(std::make_unique<tuple_processing_component_manager<Ts...>>())
//    : m_component_manager(new tuple_processing_component_manager<Ts...>())
    {
    }
    ~controller() = default;

    // Do some initialization
    void init()
    {
        m_component_manager->init();
    }

    // Process components
    void process()
    {
        // A simple loop over components. 
        notstd::for_each_type<Ts...>([&](auto tag) {
            using component_t = notstd::type_t<decltype(tag)>;

            component_t* x = m_component_manager->template prepare<component_t>();
            // Do some processing, here I just print the component id
            std::cout << x->id() << "\n";

            // Recycle. 
            m_component_manager->recycle(x);
        });
    }

    // ... more stuff
};

Executing the controller would look something along the lines

int main(int argc, char** argv)
{
    controller<component_a, component_c> c;
    c.init();
    c.process();

    return 0;
}

My problem lies in the controller class and the m_component_manager member.

If I construct a controller and initialize m_component_manager the following is working without any problem, obviously:

std::unique_ptr<tuple_processing_component_manager<Ts...>> m_component_manager = std::make_unique<tuple_processing_component_manager<Ts...>>();

How would I have to change the definition of m_component_manager to take any variadic implementation of a component_manager and/or what adjustments need to be done to accomplish this? I tried to use std::unique_ptr<iprocessing_component_manager> but that is certainly not working out. Despite that I tried to change a few things but get multiple inheritance ambiguity errors or no unique final overrider for iprocessing_component_manager::prepare().

At a later step I would like to be able to pass an instance of a component manager into the controller constructor as an argument, just to be a bit more flexible.

So I am not sure if this is possible at all what I am trying to achieve. Is there any approach to resolve this? I am not very experienced with variadic templates and meta-template programming. So I wonder if anyone can show me a way how to do that.

Already tried to search for similar problems but could not find anything closely related.

Any help is very appreciated. Thanks for your time in advance!

Andreas W. Wylach
  • 723
  • 2
  • 10
  • 31
  • If `controller` had a `std::unique_ptr m_component_manager;` which pointed at a `tuple_processing_component_manager`, what would you expect to happen when you `m_component_manager->prepare()`? – aschepler Apr 17 '18 at 00:52
  • @Aschepler I totally agree with you, just that what I get. I am just trying to find a valid way to have a unified interface around an `specialized `tuple_processing_component_manager` so that I able to pass into `controller` various different implementations. I was also thinking of creating another wrapper class with an base interface around `tuple_processing_component_manager` and fetching the pointer to it, but think that also leads me to an dead end. Major problem is I need `prepare()` as it is implemented now, to apply the type and create an instance of it. – Andreas W. Wylach Apr 17 '18 at 01:15
  • Maybe you could do something with a `icomponent* prepare(const std::type_info&)`... – aschepler Apr 17 '18 at 01:25
  • @Aschepler But would'nt that mean that I have to do RTTI checks within `prepare` to create an instance of component x? In this case I lost all the dynamic flavor. Whenever another component is created, I have to add this new type there too. – Andreas W. Wylach Apr 17 '18 at 01:33
  • At which point to you use virtual inheritance? – curiousguy Apr 26 '18 at 23:43

3 Answers3

1

I'm going to have a go at this and learn along with you. I've been working through rabbit hole of spirit x3, under the hood.

So, from what I understand, you don't have to have inheritance. You definitely don't need shared pointers and objects on the heap like you would with a mixed container of derived objects. Real objects of different types can be stored in a tuple.

I'm about done for the day but here is what I've got so far. All your stuff but with a tuple.

In controller::init I iterate the tuple to call the method say. What I need to do next is figure out how to remove the constant qualifier so you have the freedom of modifying your objects. Is this what you are looking for?

#include "stdafx.h"
//#include "utilities.h"
#include <cstdlib>
#include <iostream>
#include <tuple>

//https://stackoverflow.com/questions/26902633/how-to-iterate-over-a-stdtuple-in-c-11
template<class F, class...Ts, std::size_t...Is>
void for_each_in_tuple(const std::tuple<Ts...> & tuple, F func, std::index_sequence<Is...>) {
    using expander = int[];
    (void)expander {
        0, ((void)func(std::get<Is>(tuple)), 0)...
    };
}

template<class F, class...Ts>
void for_each_in_tuple( std::tuple<Ts...> & tuple, F func) {
    for_each_in_tuple(tuple, func, std::make_index_sequence<sizeof...(Ts)>());
}


// https://stackoverflow.com/questions/42255534 (Yakk)
namespace notstd {
    template<class T> struct tag_t { constexpr tag_t() {}; using type = T; };
    template<class T> constexpr tag_t<T> tag{};
    template<class Tag> using type_t = typename Tag::type;

    template<class...Ts, class F>
    void for_each_type(F&& f) {
        using discard = int[];
        (void)discard {
            0, (void(
                f(tag<Ts>)
                ), 0)...
        };
    }
}

// A component
class icomponent {
public:
    virtual std::string id() = 0;
    virtual ~icomponent() = default;

    virtual void init() = 0;
    virtual void dispose() = 0;

    void say() const {
        std::cout << "say: " << typeid(*this).name() << " ANDNAME: " << /*id( ).c_str( )*/"???" << std::endl;
    }
};

class component_base : public icomponent
{
public:
    virtual ~component_base() = default;

    virtual void init()
    {
        // ... init context
    }

    virtual void dispose()
    {
        // ...
    }

    // ... more
};

// Sample components
class component_a : public component_base {
public:
    virtual std::string id() override { return "component a"; }
};
class component_b : public component_base {
public:
    virtual std::string id() override { return "component b"; }
};
class component_c : public component_base {
public:
    virtual std::string id() override { return "component c"; }
};

// Interface component manager
class iprocessing_component_manager {
public:
    virtual ~iprocessing_component_manager() = default;

    virtual void init() = 0;
    virtual icomponent* prepare() = 0;
    virtual void recycle(icomponent* p) = 0;
    virtual void dispose() = 0;
};

// Implementation component manager
template<typename T>
class  type_processing_component_manager
    : public iprocessing_component_manager {
public:
    virtual ~type_processing_component_manager() = default;

    virtual T* prepare() override
    {
        // Default create T or fetch from a object pool, etc ...
        return new T;
    }
};

// Implementation virt. methods component mgr
template<typename ... Ts>
class tuple_processing_component_manager
    : public type_processing_component_manager<Ts>... {
public:
    virtual ~tuple_processing_component_manager() = default;

    virtual void init() override
    {
        std::cout << "init: " << typeid(*this).name() << std::endl;

    }

    template<typename T>
    T* prepare()
    {
        return type_processing_component_manager<T>::prepare();
    }

    virtual void recycle(icomponent* p) override
    {
        // Delete pointer or return to an object pool, etc
        delete p;
    }

    virtual void dispose() override
    {

    }
};

// The controller
template <typename... Ts>
class controller  {

    std::tuple< Ts...> tups;

public:
    controller()
    {
    }
    ~controller() = default;

    // Do some initialization
    // https://stackoverflow.com/questions/16387354/template-tuple-calling-a-function-on-each-element
    void init()
    {
        for_each_in_tuple(tups, [](auto &x) { x.say(); });
//      m_component_manager->init();
    }

    // Process components
    void process()
    {
        // A simple loop over components. 

        //but this does not get the stored objects!
        //notstd::for_each_type<Ts...>([&](auto tag) {
        //  using component_t = notstd::type_t<decltype(tag)>;

    }

    // ... more stuff
};

int main(int argc, char** argv)
{
    controller<component_a, component_c> c;
    c.init();
    c.process();

    return 0;
}
lakeweb
  • 1,859
  • 2
  • 16
  • 21
  • Thanks for that approach. I will have a closer look at this today. I surely be back on that:-) – Andreas W. Wylach Apr 17 '18 at 01:47
  • A quick glance at your code shows me one issue: I am not able to pass into the controller a distinct component manager implementation as an instance. At the end I will have 3 different mangers to choose from: A simple manager that just creates an component instance or deletes it when done. Another manager is a pooled version, where I fetch component pointers from an object pool and recycle by returning them back into the pool. A third manager would have additional component caching (that is a specific case). In short: I need to be able to create component manager instances for the controller. – Andreas W. Wylach Apr 17 '18 at 02:01
1

Here's the gist of what I was suggesting with using std::type_info:

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

    virtual void v_add_manager(
        const std::type_info& type,
        std::unique_ptr<iprocessing_component_manager> manager) = 0;
    virtual void v_forget_manager(
        const std::type_info& type) = 0;
    virtual icomponent* v_prepare(
        const std::type_info& type) = 0;
    virtual void recycle(icomponent* comp) = 0;
};

class multitype_processing_component_manager
{
public:
    void v_add_manager(
        const std::type_info& type,
        std::unique_ptr<iprocessing_component_manager> manager)
        override
    {
        // This would replace an existing manager.
        // Could also choose to throw or do nothing if already exists...
        m_managers[type] = std::move(manager);
    }

    template <class T>
    void add_manager(std::unique_ptr<iprocessing_component_manager> manager
                         = nullptr)
    {
        if (!manager)
            manager = std::make_unique<type_processing_component_manager<T>>();
        v_add_manager(typeid(T), std::move(manager));
    }

    void v_forget_manager(const std::type_info& type) override
    {
        m_managers.erase(type);
    }

    template <class T>
    void forget_manager()
    {
        v_forget_manager(typeid(T));
    }

    icomponent* v_prepare(const std::type_info& type) override
    {
        // Throws if type unknown.
        return m_managers.at(type)->prepare();
    }

    template <class T>
    T* prepare()
    {
        return dynamic_cast<T*>(v_prepare(typeid(T)));
    }

    void recycle(icomponent* comp) const override
    {
        // Throws if type unknown.
        m_managers.at(typeid(*comp))->recycle(comp);
    }

    template <typename F>
    for_each_manager(F&& func) const
    {
        for (auto& pair : m_managers)
            func(*pair.second);
    }

private:
    std::unordered_map<std::type_index,
        std::unique_ptr<iprocessing_component_manager>> m_managers;
};

controller can contain either the above interface or derived class and expose whatever interface to it is appropriate. If other polymorphic component manager behavior is needed, other subclasses could either inherit from imultitype_processing_component_manager directly, or inherit from multitype_processing_component_manager in order to make use of and add to its support for containing mixed single-type managers. If needed, imultitype_processing_component_manager could add a virtual v_for_each_manager(const std::function<void(iprocessing_component_manager&)>& func) = 0;, but std::function adds some overhead compared to just a functor template argument.

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • Thanks for that way! I appreciate that very much. As I have mentioned in my answer, I am in the middle of the development with this part. So I will see the upcoming days what's working out best. I will definitely check your approach while working that out in the project code. I like the different design of the component manager though! – Andreas W. Wylach Apr 17 '18 at 13:33
0

After taking a break from the code (sometimes this is a big help) I also want to come up with the solution I've worked out.

I basically changed a the interface of the controller. Instead of passing in the single component types as a variadic argument, I simply pass in the processing_component_manager with its variadic argument.

The controller now looks like this:

template <typename T> 
class controller {
private:
    std::unique_ptr<T> m_component_manager;

public:
    controller()
    : m_component_manager(std::make_unique<T>())
    {
    }
    ~controller() = default;

    void init()
    {
        m_component_manager->init();
    }

    void process()
    {
        std::cout << m_component_manager->m_component_ts_size << "\n";

        for_each_in_tuple(m_component_manager->m_component_ts, [&](auto tag) {
            using component_t = decltype(tag);

            std::unique_ptr<component_t> p{
                m_component_manager->template prepare<component_t>()
            };

            std::cout << p->id() << "\n";

            // ...

            m_component_manager->recycle(p.get());            
        });
    }
};

In the tuple_processing_component_manager I just added another member that converts the variadic arguments - my components - into a tuple:

const std::tuple<Ts...> m_component_ts;

Now I can iterate over that tuple of types within the process method of the controller. All the remaining code is just as posted in my question.

The usage of the controller looks like this:

controller<simple_processing_component_manager<component_a, component_c>> c;
c.init();
c.process();

With this I can - aside the simple tuple_processing_component_manager for one shot runs - create additional managers, like stated, using an object pool for pointer re-usage / recycling if used in a long run process and add a few more specialized managers, whatever is needed.

I should mention that I am only passing in one type of component manager using n components. An individual component management can be seen as a strategy how to handle the components at runtime.

For the time being, that way seems to me reasonable and theoretical working out. I haven't implemented that code in my project yet. So I will see if does its thing as expected or not.

Andreas W. Wylach
  • 723
  • 2
  • 10
  • 31