I'm trying to reference a member of a (yet) unknown instance from within a boost::spirit rule definitions' action, so in pseudocode,
instead of double_[ref(rN) = _1] I'm looking for something like X** ppx; double_[ref(&X::rN, ppx) = _1]
A workaround for it could be a simple "semantic action" with a parameter which knows the instance and would be able to write to it, like
qi::rule<Iterator, Skipper> start;
my_grammar(DataContext*& dataContext) : my_grammar::base_type(start) , execContext(execContext) {
start = qi::double_[ boost::bind(&my_grammar::newValueForXY, dataContext, ::_1) ];
However, I'm wondering if there is a possibility to "bind" directly to the member variable like it's possible to bind to a "local" variable by using "phoenix::ref(...) = value".
I tried the following syntax:
start = qi::int_[ boost::bind<int&>(&DataContext::newValueForXY, boost::ref(dataContext))() = ::_1] ];
but failed with VS2010SP1 and the error message
error C2440: '=': 'boost::arg' cannot be converted into ...
There are several ways to skin this cat:
- you probably thought of deferring execution of the bind expression:
phx::bind
can do that - alternatively, you could just use attribute propagation for that (and do without semantic actions altogether)
- lastly, you could use inherited attributes (e.g. when the DataContext has no default constructor or copying is expensive)
1. Deferring the Bind with Phoenix
Use phoenix bind for the purpose: this will result in a Phoenix actor, that will be "deferred executed" at the time the semantic action is triggered.
Here's a reconstruction of the missing code sample you might have been after:
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace qi = boost::spirit::qi;
namespace phx= boost::phoenix;
struct DataContext
{
double xy;
};
template <typename Iterator, typename Skipper>
struct my_grammar : qi::grammar<Iterator, Skipper>
{
my_grammar(DataContext& dataContext) : my_grammar::base_type(start)
{
start = qi::double_
[ phx::bind(&my_grammar::newValueForXY,
phx::ref(dataContext),
qi::_1) ];
}
private:
static void newValueForXY(DataContext& dc, double value)
{
dc.xy = value;
}
qi::rule<Iterator, Skipper> start;
};
int main()
{
const std::string s = "3.14";
DataContext ctx;
my_grammar<decltype(begin(s)), qi::space_type> p(ctx);
auto f(begin(s)), l(end(s));
if (qi::phrase_parse(f, l, p, qi::space))
std::cout << "Success: " << ctx.xy << "\n";
}
Note:
- phx::ref() to wrap the reference to the datacontext
- qi::_1 instead of boost::_1 as a placeholder
given this implementation of
newValueForXY
you could just as easily have writtenstart = qi::double_ [ phx::bind(&DataContext::xy, phx::ref(dataContext)) = qi::_1 ];
2. Use an attribute grammar, instead of semantic actions
However, I'd probably write the same example using attributes instead of semantic actions (because that's basically what they are for):
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace qi = boost::spirit::qi;
namespace phx= boost::phoenix;
struct DataContext {
double xy;
};
BOOST_FUSION_ADAPT_STRUCT(DataContext, (double, xy))
template <typename Iterator, typename Skipper>
struct my_grammar : qi::grammar<Iterator, DataContext(), Skipper>
{
my_grammar() : my_grammar::base_type(start) {
start = qi::double_;
}
private:
qi::rule<Iterator, DataContext(), Skipper> start;
};
int main()
{
const std::string s = "3.14";
static const my_grammar<decltype(begin(s)), qi::space_type> p;
DataContext ctx;
if (qi::phrase_parse(begin(s), end(s), p, qi::space, ctx))
std::cout << "Success: " << ctx.xy << "\n";
}
3. Use inherited attributes to pass in the context reference
If you absolutely insist, you can even use inherited attributes
for the purpose:
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace qi = boost::spirit::qi;
namespace phx= boost::phoenix;
struct DataContext {
double xy;
};
template <typename Iterator, typename Skipper>
struct my_grammar : qi::grammar<Iterator, void(DataContext&), Skipper> {
my_grammar() : my_grammar::base_type(start)
{
start = qi::double_ [ phx::bind(&DataContext::xy, qi::_r1) = qi::_1 ];
}
qi::rule<Iterator, void(DataContext&), Skipper> start;
};
int main() {
const std::string s = "3.14";
const static my_grammar<std::string::const_iterator, qi::space_type> p;
DataContext ctx;
if(qi::phrase_parse(begin(s), end(s), p(phx::ref(ctx)), qi::space)) {
std::cout << "Success: " << ctx.xy << "\n";
}
}
This is somewhat more expressive at the call site:
qi::phrase_parse(begin(s), end(s), p(phx::ref(ctx)), qi::space));
and doesn't require the context to be default constructible.
来源:https://stackoverflow.com/questions/16675368/is-it-possible-to-attach-an-action-to-a-boostspiritrule-parser-which-assigns