Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
284 views
in Technique[技术] by (71.8m points)

c++ - Using boost::bind with boost::function: retrieve binded variable type

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();
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...