10

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 ?

codablank
  • 181
  • 2
  • 9
  • 2
    Why not just use a set parameter list, and let the user use `std::bind` to bind extra parameters as needed? – Nicol Bolas Oct 02 '11 at 01:33
  • 1
    C++ doesn't have type erasure. Class templates are *templates* for classes (not "generic classes" like in Java or C#). When you instantiate a class template, the instances are brand new classes that have no relationship to each other (except for their name). – Ken Bloom Oct 02 '11 at 01:36
  • >Nicol What do you mean by a set parameter list ? Could you provide an example, I don't see it – codablank Oct 02 '11 at 01:59
  • 1
    @codablank: I mean a fixed list of parameters. All functions take the same parameters. And if someone wants to use a function that takes other parameters, they have to use `std::bind` to make the adjustments. – Nicol Bolas Oct 02 '11 at 02:42
  • 1
    Rather than play around with `std::function` it would probably be better design to make `Command` an abstract class (interface) and create two different subclasses for two different kinds of commands. – han Oct 02 '11 at 07:13
  • @NicolBolas: Ok but that means the users will have to add `bind(&Print::print1, &p, string("test1"), string("test2"))` thelmselves ? Or just something like `bind(string("test1"), string("test2"))`? – codablank Oct 02 '11 at 15:38

3 Answers3

10

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;
};
David Feurle
  • 2,687
  • 22
  • 38
  • Actually I'm under gcc 4.5 so I can use variadic templates; there would be only a few changes in your code, though – codablank Oct 02 '11 at 15:46
  • @codablank after you changed your question I changed the answer to use variadic templates. It changes the need to have a class for every argument count and the overload for the execute function for argument count -> to me it is a lot more readable with variadic template support – David Feurle Oct 02 '11 at 16:28
  • Very great! Nice base for implementing reflection in C++. – Simon Ninon Jul 03 '15 at 21:18
5

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.

pmr
  • 58,701
  • 10
  • 113
  • 156
  • _Your users can then modify their functions using bind to match the signature you require._ yeah, NicolBolas provided me the same advice, but I actually don't see it, could you show me some code ? – codablank Oct 02 '11 at 15:45
  • @codablank I added loads of code for your original example and some explanation. – pmr Oct 02 '11 at 16:00
  • @codablank Not as far as I know. I've seen two different implementations of this kind of thing and `boost::any` is the most efficient. Anyway, it's your only option and if speed is what you are after, your approach is certainly wrong and you should have said so in the question. – pmr Oct 02 '11 at 18:13
  • @pmr I like your approach since it directly stores the function. Why do you need two overloads of the call function? With variadic template it could be 0 to ~ arguments. – David Feurle Oct 02 '11 at 18:50
  • @David Exactly, hence the comment. This is just a sample so OP knows how to implement it. I think it would also be important to prevent the template arguments in the `call` function from being deduced as it can lead to unexpected results if the user doesn't fully understand the deduction mechanism. – pmr Oct 02 '11 at 19:00
2

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.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243