Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using boost::bind with boost::function: retrieve binded variable type

Tags:

c++

types

boost

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();
}
like image 963
Smash Avatar asked Feb 23 '23 14:02

Smash


2 Answers

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);
}

like image 53
sehe Avatar answered Feb 25 '23 05:02

sehe


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).

like image 30
Xeo Avatar answered Feb 25 '23 05:02

Xeo