Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++0x : Storing any type of std::function in a std::map

Tags:

c++

c++11

bind

I'm trying to store a set of std::function in a map (under GCC 4.5)

I'd like to get 2 kind of things :

  • storing functions with arguments already passed; then you just have to call f()
  • storing functions without arguments; then you have to call f(...)

I think I achieved the first one with a class Command and a Manager :

class Command
{
  std::function<void()> f_;
  public:
    Command() {}
    Command(std::function<void()> f) : f_(f) {}

    void execute() { if(f_) f_(); }

};

class CommandManager
{
  typedef map<string, Command*> FMap;

  public :

  void add(string name, Command* cmd)
  {
     fmap1.insert(pair<string, Command*>(name, cmd));
  }

  void execute(string name)
  {
    FMap::const_iterator it = fmap1.find(name);
    if(it != fmap1.end())
    {
      Command* c = it->second;
      c->execute();
    }
  }

  private :

    FMap fmap1;

};

can be used like this :

class Print{

   public:
   void print1(string s, string s1){ cout<<"print1 : "<<"s : "<<s<<" s1 : "<<s1<<endl; }
   int print2(){ cout<<"print2"<<endl; return 2;}

};

#include <string>
#include <functional>

int main()
{
  Print p = Print();

  function<void()> f1(bind(&Print::print1, &p, string("test1"), string("test2")));

  function<int()> f2(bind(&Print::print2, &p));

  CommandManager cmdMgr = CommandManager();
  cmdMgr.add("print1", new Command(f1));
  cmdMgr.execute("print1");

  cmdMgr.add("print2", new Command(f2));
  cmdMgr.execute("print2");

  return 0;
}

Now I'd like to be able to do this :

 int main()
 {
      Print p = Print();

      function<void(string, string)> f1(bind(&Print::print1, &p, placeholders::_1, placeholders::_2));

      CommandManager cmdMgr = CommandManager();
      cmdMgr.add("print1", new Command(f1));
      cmdMgr.execute("print1", string("test1"), string("test2"));

      return 0;
    }

Is there a way, using type-erasure for example ?

like image 711
codablank Avatar asked Oct 02 '11 01:10

codablank


3 Answers

What you are trying to do is not possible without some serious runtime work and the associated cost. The simplest solution would of course to just store a boost::any (any_function never made it into boost) inside your map and do the necessary casts (or add some runtime data that tells you which cast to make), although you should avoid that at any cost and go with fixed arguments or no arguments. Your users can then modify their functions using bind to match the signature you require.

Edit: In your current scheme I see no reason for CommandManager to store Command* in the map.

Edit2: Also you drop the return type. This could be OK for your use-case but makes this a lot less generic.

Edit3: I worked out some working example of your code using any. I feel that there is some flaw and I really don't see what this should achieve but here it goes:

#include <iostream>
#include <string>
#include <map>
#include <functional>

#include <boost/any.hpp>

class AnyCaller
{
  std::map<std::string, boost::any> calls;
public:
  AnyCaller() {}

  void add(const std::string& name, const boost::any& fun) {
    calls[name] = fun;
  }

  // we always use the throwing version of any_cast
  // icbb by error checking

  // no arg version
  template<typename Ret>
  Ret call(const std::string& s) {
    const boost::any& a = calls[s];
    return boost::any_cast< std::function<Ret(void)> >(a)();
  }

  // this should be a variadic template to be actually usable
  template<typename Ret, typename T>
  Ret call(const std::string& s, T&& arg) {
    // we have to assume that our users know what we are actually returning here
    const boost::any& a = calls[s];
    return boost::any_cast< std::function<Ret(T)> >(a)(std::forward<T>(arg));
  }

  virtual ~AnyCaller() {}
};

int foo() { std::cout << "foo" << std::endl; return 1; }
double foo2(int i) { std::cout << "foo2" << std::endl; return double(i); }

int main()
{
  AnyCaller c; 
  c.add("foo", std::function<int(void)>(foo));
  c.add("foo2", std::function<double(int)>(foo2));

  c.call<int>("foo");
  c.call<double, int>("foo2", 1);
  // this should throw
  c.call<double, int>("foo", 1);
  return 0;
}

As for the example using a fixed signature. Just think of what would be the most natural representation of a function you are going to store (looking at your Command example I'd assume it is std::function<void(void)>. Store functions of this type and whenever one your users tries to use it, he has to bind whatever function he wants to use, so it matches this signature.

like image 156
pmr Avatar answered Nov 15 '22 13:11

pmr


You could use dynamic cast to determine the type of the function in the list at runtime. Please note that I added shared_ptr to remove the memory leak in the original sample. Perhaps you want to throw a exception if the execute method is called with the wrong arguments (if the dynamic_cast yields 0).

Usage:

void x() {}
void y(int ) {}
void main() {
    CommandManager m;
    m.add("print", Command<>(x));
    m.add("print1", Command<int>(y));
    m.execute("print");
    m.execute("print1", 1);
}

Code (with variadic template support for example gcc-4.5):

#include <functional>
#include <map>
#include <string>
#include <memory>

using namespace std;

class BaseCommand
{
public:
    virtual ~BaseCommand() {}
};

template <class... ArgTypes>
class Command : public BaseCommand
{
  typedef std::function<void(ArgTypes...)> FuncType;
  FuncType f_;
  public:
    Command() {}
    Command(FuncType f) : f_(f) {}
    void operator()(ArgTypes... args) { if(f_) f_(args...); }
};

class CommandManager
{
  typedef shared_ptr<BaseCommand> BaseCommandPtr;
  typedef map<string, BaseCommandPtr> FMap;
  public :

  template <class T>
  void add(string name, const T& cmd)
  {
     fmap1.insert(pair<string, BaseCommandPtr>(name, BaseCommandPtr(new T(cmd))));
  }

  template <class... ArgTypes>
  void execute(string name, ArgTypes... args)
  {
    typedef Command<ArgTypes...> CommandType;
    FMap::const_iterator it = fmap1.find(name);
    if(it != fmap1.end())
    {
      CommandType* c = dynamic_cast<CommandType*>(it->second.get());
      if(c)
      {
    (*c)(args...);
      }
    }
  } 

  private :
    FMap fmap1;
};

without variadic template support (example VS2010):

#include <functional>
#include <map>
#include <string>
#include <memory>

using namespace std;
class Ignored;

class BaseCommand
{
public:
    virtual ~BaseCommand() = 0 {};
};

template <class A1 = Ignored>
class Command : public BaseCommand
{
  typedef std::function<void(A1)> FuncType;
  FuncType f_;
  public:
    Command() {}
    Command(FuncType f) : f_(f) {}
    void operator()(const A1& a1) { if(f_) f_(a1); }
};

template <>
class Command<Ignored> : public BaseCommand
{
  typedef std::function<void()> FuncType;
  FuncType f_;
  public:
    Command() {}
    Command(FuncType f) : f_(f) {}
    void operator()() { if(f_) f_(); }
};

class CommandManager
{
  typedef shared_ptr<BaseCommand> BaseCommandPtr;
  typedef map<string, BaseCommandPtr> FMap;
  public :

  template <class T>
  void add(string name, const T& cmd)
  {
     fmap1.insert(pair<string, BaseCommandPtr>(name, BaseCommandPtr(new T(cmd))));
  }

  template <class A1>
  void execute(string name, const A1& a1)
  {
    typedef Command<A1> CommandType;
    FMap::const_iterator it = fmap1.find(name);
    if(it != fmap1.end())
    {
      CommandType* c = dynamic_cast<CommandType*>(it->second.get());
      if(c)
      {
        (*c)(a1);
      }
    }
  } 

  void execute(string name)
  {
    typedef Command<> CommandType;
    FMap::const_iterator it = fmap1.find(name);
    if(it != fmap1.end())
    {
      CommandType* c = dynamic_cast<CommandType*>(it->second.get());
      if(c)
      {
        (*c)();
      }
    }
  }
  private :
    FMap fmap1;
};
like image 36
David Feurle Avatar answered Nov 15 '22 14:11

David Feurle


Your Command class constructor needs a function<void()>. You are trying to feed it a function<void(string,string)>. This is not going to typecheck.

If you need functions that accept variable arguments (like printf), you will need function<> and execute() that accept variable arguments. You need to know how to work with that (in particular, you need a fixed first argument). You are then responsible for type safety, much like with printf.

If you just need a variable number of string arguments, use functions that accept e.g. vectors of strings.

All this has nothing to do whatsoever with std::map. Whatever you can store in a plain old variable, you can store in std::map too.

like image 2
n. 1.8e9-where's-my-share m. Avatar answered Nov 15 '22 12:11

n. 1.8e9-where's-my-share m.