Home > OS >  How to track members and call their same-name method at one place at compile time?
How to track members and call their same-name method at one place at compile time?

Time:03-25

I've got some widgets of different types. The widgets all have a method Update().

A top widget with member widgets needs to call all its members' Update() method in its UpdateMembers() method.

struct WidgetA{
    void Update();
};
struct WidgetB{
    void Update();
};
struct TopWidget{
    WidgetA w1;
    WidgetB w2;
    //other widgets
    
    void UpdateMembers(){
        w1.Update();
        w2.Update();
        //other widgets
    }
};

Manually updating UpdateMembers() when TopWidget's member list changes is error prone, so I need to do that automatically. The following code does the job, but it uses global states and has overhead in space and time. How can I do its job with some compile time programming?

#include <vector>
struct Updatable;

static std::vector<Updatable*> g_vec;

struct Updatable{
    Updatable(){g_vec.push_back(this);}
    virtual void Update()=0;
    virtual ~Updatable();
};

struct WidgetA:Updatable{
    void Update()override;
};
struct WidgetB:Updatable{
    void Update()override;
};
struct TopWidget{
    WidgetA wa;
    WidgetB wb;
    //other widgets

    void UpdateMembers(){
        for(auto w:g_vec){
            w->Update();
        }
    }
    ~TopWidget(){
        g_vec.clear();
    }
};

CodePudding user response:

As you ask for compile time solution without dynamic registering stuff, you may want something like the following:

struct WidgetA{
    void Update(){ std::cout << "Update A" << std::endl;}
};
struct WidgetB{
    void Update(){ std::cout << "Update B" << std::endl;}
};

template < typename ... CHILD_WIDGETS >
struct TopWidget{
    //other widgets
    std::tuple< CHILD_WIDGETS...> childs{};

    void UpdateMembers(){
        std::apply([](auto&&... element) { ((element.Update()),...); }, childs );
    }    
};

int main()
{
    TopWidget< WidgetA, WidgetB > tw;
    tw.UpdateMembers();
}

In this example all member widget types are known at compile time which makes it possible to store all this types as a tuple. And execute a function for every tuple element is quite easy as we have std::apply which does the job.

BTW: There is no need to make the whole class a template, maybe it is enough to have the tuple elements created directly if you do not need to have the same class with different member widget types in the same time.

see it running

CodePudding user response:

Don't use global state. Let the Updateable register themself at their parent:

#include <vector>

struct Updatable;

struct Updater{
    std::vector<Updatable*> g_vec;
    void register_for_updates(Updatable* u){
        g_vec.push_back(u);
    }
    void update();
};

struct Updatable{
    Updatable(Updater* u) { u->register_for_updates(this);}
    virtual void Update()=0;
    virtual ~Updatable();
};

void Updater::update() {
    for (const auto u : g_vec) {
        u->Update();
    }
}

struct WidgetA:Updatable{
    WidgetA(Updater* u) : Updatable(u) {}
    void Update()override;
};
struct WidgetB:Updatable{
    WidgetB(Updater* u) : Updatable(u) {}
    void Update()override;
};
struct TopWidget : Updater {
    WidgetA wa;
    WidgetB wb;
    TopWidget() : wa(this),wb(this) {}
    void UpdateMembers(){
        Updater::update();
    }
};

... but it uses global states and has overhead in space and time

Not sure what you call "overhead". If it is the vector, then thats not really overhead, but just what you need to get the job done. As long as C has no reflection you need to keep track of members to be updated yourself.

  •  Tags:  
  • c
  • Related