plamkata__
plamkata__

Reputation: 763

C++ call method passed as argument

I have a Session class that defines multiple Send methods that takes different arguments. For example:

class Session
{
public:
    void SendInt(int i);
    void SendTwoInts(int i1, int i2);
    void SendStringAndInt(const std::string& str, int i);
};

Also I have SessionsManager class that holds all sessions.

class SessionsManager
{
private:
    std::vector<Session*> m_Sessions;
    ...
};

I'd like to add broadcast methods to SessionsManager class that calls the same method for each session. If I just define a separate broadcast method for each Session Send method I will end like:

class SessionsManager
{
public:
     void BroadcastInt(int i) { for(auto it : m_Sessions) { it->SendInt(i); } };
     void BroadcastTwoInts(int i1, int i2) { for(auto it : m_Sessions) { it->SendTwoInts(i1, i2); } };
...
};

It is too much copy-paste and in theory the number of Send methods will grow in the future. I'd like something smarter.

In the perfect scenario I imagine having templated Broadcast method that takes Session method and its arguments as arguments, i.e. something like:

template<typename Method, typename ...Args)
void Broadcast(Method, Args ... args)
{
...
}

and the broadcast call will be

Broadcast(&Session::SendInt, 2);
Broadcast(&Session::SendTwoInts, 2, 3);

The problem is that I am not sure if it is possible and how exactly to implement Broadcast. I am thinking around std::function and std::bind but still I am unable to compile my code.

Any ideas are welcome.

It is possible to have 2 Send methods with the same arguments but with different semantic. For example:

void SendName(const std::string& name);
void SendDescription(const std::string& description);

Upvotes: 2

Views: 261

Answers (3)

Neel Basu
Neel Basu

Reputation: 12904

I don't know what you are really trying to achive. It looks like one SessionsManager have multiple Sessions. Performing an action on SessionManager relays that action to all its Sessions

and you have a Broadcaster that sends commands to SessionManager

struct SessionManager{
   template <typename F>
   void broadcast(F f){
     std::for_each(m_sessions.begin(), m_sessions.end(), f);
   }

   std::vector<Session*> m_sessions;
};

Now you do boost::bind and send them to SessionManager::broadcast

Upvotes: 0

Dmitry Galchinsky
Dmitry Galchinsky

Reputation: 2201

A solution with std::bind could look like

#include <iostream> 
#include <functional> 
#include <vector> 

class Session
{
public:
    void SendInt(int i){ std::cout << i; }
    void SendTwoInts(int i1, int i2){ std::cout << i1;}
};

class SessionsManager
{
public:
   std::vector<Session*> m_Sessions;
    template<typename T, typename ...Args>
    void Broadcast(T f, Args&& ...args) {
        for (auto it : m_Sessions) {
            std::bind(f, it, std::forward<Args>(args)...)();
        }
    }
};


int main() {
   SessionsManager m;
   m.m_Sessions.push_back(new Session());
   m.m_Sessions.push_back(new Session());
   m.Broadcast(&Session::SendInt, 2);
   m.Broadcast(&Session::SendTwoInts, 3, 2);
}

Upvotes: 1

Andy Prowl
Andy Prowl

Reputation: 126412

Honestly I would solve this by using a variadic template for Broadcast, and simply overload the Send() method for different arguments.

Here is the code:

#include <vector>
#include <string>

class Session
{
public:

    void Send(int i) { }
    void Send(int i1, int i2) { }
    void Send(const std::string& str, int i) { }
};

class SessionsManager
{

public:

    template<typename... Args>
    void Broadcast(Args&&... args)
    {
        for(auto it : m_Sessions)
        {
            it->Send(std::forward<Args>(args)...);
        }
    }

private:

     std::vector<Session*> m_Sessions;

};

Here is how you would use it:

int main()
{
    SessionsManager sm;
    sm.Broadcast(1, 2);
    sm.Broadcast(1);
    sm.Broadcast("Hello", 2);
}

And here is a live example.


UPDATE:

Provided you really cannot afford overloading, this solution meets your original requirements:

#include <vector>
#include <string>

class Session
{
public:

    void SendInt(int i) { }
    void SendTwoInts(int i1, int i2) { }
    void SendStringAndInt(const std::string& str, int i) { }
};

class SessionsManager
{
public:

    template<typename M, typename... Args>
    void Broadcast(M m, Args&&... args)
    {
        for(auto it : m_Sessions)
        {
            ((*it).*m)(std::forward<Args>(args)...);
        }
    }

private:

     std::vector<Session*> m_Sessions; // You could use shared_ptr<> here

};

This is how you would use it:

int main()
{
    SessionsManager sm;
    sm.Broadcast(&Session::SendTwoInts, 1, 2);
    sm.Broadcast(&Session::SendInt, 1);
    sm.Broadcast(&Session::SendStringAndInt, "Hello", 1);
}

And here is a live example.

Upvotes: 4

Related Questions