4

Is there any way to retrieve information on what paramaters were bounded by boost::bind or does this need to be stored manually?

i.e.:

in .h

class MyClass
{
    void foo(int a);
    void foo2(double b);
    void bar();
    void execute();
    int _myint;
    double _mydouble;
}

in .cpp

MyClass::bar()
{
    vector<boost::function<void(void)> myVector;
    myVector.push_back(boost::bind(&MyClass::foo, this, MyClass::_myint);
    myVector.push_back(boost::bind(&MyClass::foo2, this, MyClass::_mydouble);
}
MyClass::execute(char* param)
{

    boost::function<void(void)> f  = myVector[0];
    //MAGIC goes here
    //somehow know that an int parameter was bound
    _myint = atoi(param);
    //--------------------------------------
    f();
}
Smash
  • 3,722
  • 4
  • 34
  • 54
  • 2
    This is an interesting theoretical question but I don't think your example adequately shows *why* you need to do this. – Mark B Oct 25 '11 at 18:31
  • The _why_ is I am implementing command line user input for my application. I want to be able to call pre-existing functions of various classes: `/exec `. The vector is actually a map with an associated string for the `` call... If you have a better idea of how to achieve this, please tell :D – Smash Oct 25 '11 at 20:27
  • @Smash: are you inventing a script engine? – sehe Oct 25 '11 at 21:55
  • @sehe if you are thinking so, then I probably was involuntarily :) – Smash Oct 26 '11 at 13:30

4 Answers4

6

Since it looks like you are just looking for ways to trigger functions in response to parsed text, I propose this Boost Spirit parser based example:

Goal, sample service

I want to be able to call pre-existing functions of various classes: /exec <functionName> <param1> <param2>

Imagine your application has the following existing classes which represent services that the user should be able to call into using text commands:

struct Echo
{
    void WriteLine(const std::string& s) { std::cout << "WriteLine('"     << s << "');" << std::endl; }
    void WriteStr (const std::string& s) { std::cout << "Write(string: '" << s << "');" << std::endl; }
    void WriteInt (int i)                { std::cout << "Write(int: "     << i <<  ");" << std::endl; }
    void WriteDbl (double d)             { std::cout << "Write(double: "  << d <<  ");" << std::endl; }
    void NewLine  ()                     { std::cout << "NewLine();"                    << std::endl; }
} echoService;

struct Admin
{
    void Shutdown(const std::string& reason, int retval) 
    { 
        std::cout << "Shutdown(reason: '" << reason << "', retval: " << retval << ")" << std::endl;
        // exit(retval);
    }
} adminService;

Approach and Explanation (TL;DR? skip this)

So how do we tie line-based input to this 'interface'? There are two jobs

  • parse input
  • evaluate input

You have been emphasizing on providing infrastructure for evaluation. However, you run into the problem of not knowing what parameters to pass. You know when parsing, of course. You would really like to avoid having to store the arguments in a generic container. (Of course, you could throw boost optionals, boost (recursive) variants, boost any at it all at once, but let's face it: that is still tedious busy-work).

This is an excellent opportunity for a parser with semantic actions. Lex/yacc, Coco/R C++, ANTLR and many more all support them. So does Boost Spirit Qi.

Without further ado, this is what a complete, minimalist line grammar could look like for the services described above:

parser = "/execute" > (
        (lit("WriteLine") > stringlit)
      | (lit("Write")    >> +(double_ | int_ | stringlit))
      | lit("NewLine")
      | (lit("Shutdown")  > (stringlit > -int_))

// stringlit is just a quoted string:    
stringlit = lexeme[ '"' >> *~char_('"') >> '"' ];

Note: I decided to show you how you can accept arbitrary numbers of arguments of various types to the /execute Write call

Adding the semantic actions to get tie this to our echoService and adminService objects, we get this REPL engine to parse and evaluate a single line:

void execute(const std::string& command)
{
    typedef std::string::const_iterator It;
    It f(command.begin()), l(command.end());

    if (!phrase_parse(f,l, "/execute" > (
            (lit("WriteLine")  
                > stringlit  [ bind(&Echo::WriteLine, ref(echoService), _1) ])
          | (lit("Write") >> +(
                  double_    [ bind(&Echo::WriteDbl,  ref(echoService), _1) ]
                | int_       [ bind(&Echo::WriteInt,  ref(echoService), _1) ]
                | stringlit  [ bind(&Echo::WriteStr,  ref(echoService), _1) ]
            ))
          | (lit("NewLine")  [ bind(&Echo::NewLine,   ref(echoService)) ])
          | (lit("Shutdown")  > (stringlit > (int_ | attr(0))) 
                             [ bind(&Admin::Shutdown, ref(adminService), _1, _2) ])
        ), space))
    {
        // handle error, see full code below
    }
}

Now all that's left for us to do, is program a main loop:

int main()
{
    std::string command;
    while (std::getline(std::cin, command))
        execute(command);
}

That's pretty simple, not?


Complete working example program

I posted a complete working example of this program on github 1: https://gist.github.com/1314900

It has

  • complete error handling / reporting added
  • uses an input file instead of std::cin for (ease of testing)
  • will help you get this started (namespaces, includes)

All you need is Boost. I tested this with g++ 4.6.1 (no special options) and Boost 1.47. For the following test input (input.txt):

/execute WriteLine "bogus"
/execute Write     "here comes the answer: "
/execute Write     42
/execute Write     31415e-4
/execute Write     "that is the inverse of" 24 "and answers nothing"
/execute Shutdown  "Bye" 9
/execute Shutdown  "Test default value for retval"

The output of the demo program is

WriteLine('bogus');
Write(string: 'here comes the answer: ');
Write(double: 42);
Write(double: 3.1415);
Write(string: 'that is the inverse of');
Write(double: 24);
Write(string: 'and answers nothing');
Shutdown(reason: 'Bye', retval: 9)
Shutdown(reason: 'Test default value for retval', retval: 0)
  • Note: I commented out the exit(...) call so I could demo how you could make the retval parameter optional while supplying a default value (attr(0))
  • Note: how the double 31415e-4 is prints correctly as 3.1415
  • Note: how the multiple parameters of /execute Write get translated in separate calls to echoService.Write(...) depending on the actual type of the input parameter
  • Note: how the existence of inline whitespace (outside of string literals) is completely ignored. You can use as many tabs/spaces as you like

1 In the interest of posterity, should github cease to host my gist:

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <fstream>

namespace qi  = boost::spirit::qi;
namespace phx = boost::phoenix;

///////////////////////////////////
// 'domain classes' (scriptables)
struct Echo
{
    void WriteLine(const std::string& s) { std::cout << "WriteLine('"     << s << "');" << std::endl; }
    void WriteStr (const std::string& s) { std::cout << "Write(string: '" << s << "');" << std::endl; }
    void WriteInt (int i)                { std::cout << "Write(int: "     << i <<  ");" << std::endl; }
    void WriteDbl (double d)             { std::cout << "Write(double: "  << d <<  ");" << std::endl; }
    void NewLine  ()                     { std::cout << "NewLine();"                    << std::endl; }
} echoService;

struct Admin
{
    void Shutdown(const std::string& reason, int retval) 
    { 
        std::cout << "Shutdown(reason: '" << reason << "', retval: " << retval << ")" << std::endl;
        // exit(retval);
    }
} adminService;

void execute(const std::string& command)
{
    typedef std::string::const_iterator It;
    It f(command.begin()), l(command.end());

    using namespace qi;
    using phx::bind;
    using phx::ref;

    rule<It, std::string(), space_type> stringlit = lexeme[ '"' >> *~char_('"') >> '"' ];

    try
    {
        if (!phrase_parse(f,l, "/execute" > (
                (lit("WriteLine")  
                    > stringlit  [ bind(&Echo::WriteLine, ref(echoService), _1) ])
              | (lit("Write") >> +(
                      double_    [ bind(&Echo::WriteDbl,  ref(echoService), _1) ] // the order matters
                    | int_       [ bind(&Echo::WriteInt,  ref(echoService), _1) ]
                    | stringlit  [ bind(&Echo::WriteStr,  ref(echoService), _1) ]
                ))
              | (lit("NewLine")  [ bind(&Echo::NewLine,   ref(echoService)) ])
              | (lit("Shutdown")  > (stringlit > (int_ | attr(0))) 
                                 [ bind(&Admin::Shutdown, ref(adminService), _1, _2) ])
            ), space))
        {
            if (f!=l) // allow whitespace only lines
                std::cerr << "** (error interpreting command: " << command << ")" << std::endl;
        }
    }
    catch (const expectation_failure<It>& e)
    {
        std::cerr << "** (unexpected input '" << std::string(e.first, std::min(e.first+10, e.last)) << "') " << std::endl;
    }

    if (f!=l)
        std::cerr << "** (warning: skipping unhandled input '" << std::string(f,l) << "')" << std::endl;
}

int main()
{
    std::ifstream ifs("input.txt");

    std::string command;
    while (std::getline(ifs/*std::cin*/, command))
        execute(command);
}

sehe
  • 374,641
  • 47
  • 450
  • 633
  • thanks for all of this, I already use a parser which separates the inputs into various `char*` arrays, so I get by `/set` and `/get` commands, but I'll probably use this for a more wide-range "execute any other function with parameters" portion. You get a nice big upvote :D, but I can't really accept your answer _yet_ since it doesn't answer the original question. – Smash Oct 26 '11 at 13:28
  • Ah, perils of **[YX answering](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem)** :) For the record, I don't mind, I was well aware I was answering something else then your explicit question. Sometimes, I just (a) try to help in the way I am most motivated to (b) I learn from it myself - if only dexterity. Cheers! – sehe Oct 26 '11 at 16:06
2

As far as I know, you can't do what you want, as std::function throws away (nearly) all type information of the passed function pointer, function object or lambda. It employs a technique called type erasure and on the surface completely forgets what was passed into it, as long as it is callable with the provided arguments. So, after binding, you're out of luck it seems.

However, you can supply that information yourself:

#include <functional>
#include <sstream>
#include <string>
#include <vector>

struct call_info{
  std::function<void(void)> func;
  std::string arg_type;
};

class MyClass{
  std::vector<call_info> _myVec;
  int _myInt;
  double _myDouble;

public:
  void foo1(int i);
  void foo2(double d);
  void bar();
  void execute(char* param);
};

void MyClass::bar(){
  call_info info;
  info.func = std::bind(&MyClass::foo1, this, &MyClass::_myInt);
  info.arg_type = "int";
  _myVector.push_back(info);
  info.func = std::bind(&MyClass::foo2, this, &MyClass::_myDouble);
  info.arg_type = "double";
  _myVector.push_back(info);
}

void MyClass::execute(char* param){
  call_info& info = _myVector[0];
  std::stringstream conv(param);

  if(info.arg_type == "int")
    conv >> _myInt;
  else if(info.arg_type == "double")
    conv >> _myDouble;
  info.func();
}

Not nearly as clean as having it supplied automatically, but as far as I know there's no better way (except completely changing your implementation like sehe proposes).

Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
0

boost::function has some public typedefs which you should be able to use.

template<typename Signature>  // Function type R (T1, T2, ..., TN)
class function : public functionN<R, T1, T2, ..., TN> {
public:
  // types
  typedef R  result_type;         
  typedef T1 argument_type;         // If N == 1
  typedef T1 first_argument_type;   // If N == 2
  typedef T2 second_argument_type;  // If N == 2
  typedef T1 arg1_type;           
  typedef T2 arg2_type;  

  // ...

  // static constants
  static const int arity = N;

Adapted slightly from the boost::function reference.

Tom Kerr
  • 10,444
  • 2
  • 30
  • 46
0

Your basic problems seems to be that your interface isn't rich enough. Once you are done adding things to your vector (and thus start only using your function objects polymorphically), you should not need to look at their concrete types again. There are several ways to solve this:

Use an additional callback that stores the parsing function:

vector<boost::function<void(char*)> myParseVector;
myParseVector.push_back(boost::bind(&MyClass::parseInt, this, MyClass::_myint);
myParseVector.push_back(boost::bind(&MyClass::parseDouble, this, MyClass::_mydouble);

..or, of course, put these two callbacks into the same vector via a custom struct or a pair.

Or... use type-erasure yourself with custom made interface that supports parse and execute!

ltjax
  • 15,837
  • 3
  • 39
  • 62