Yes it's possible.
One pitfall is that presence of semantic actions usually supresses automatic attribute propagation. Since you'd like to have both, you will want to assign the parser expression using %=
instead of =
(see docs).
Alternatively you can generate a value on the fly and use the adaptation that you showed.
Proof Of Concept: SA + Adaptation
Here I'd simply exclude id
from the adaptation. Note also that you don't need to repeat types since c++11:
BOOST_FUSION_ADAPT_STRUCT(
client::employee, age, /*id, */ surname, forename, salary)
I'd prefer to write the SA with some phoenix function helpers:
auto _id = px::function { std::mem_fn(&client::employee::id) };
auto _gen = px::function { client::employee::generate_id };
start %= skip(space) [
age >> name >> name >> salary >> eps
[ _id(_val) = _gen() ]
];
Live On Coliru
#include <boost/fusion/adapted.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <iomanip>
#include <iomanip>
namespace qi = boost::spirit::qi;
namespace px = boost::phoenix;
namespace fus = boost::fusion;
namespace client {
struct employee {
std::string id;
int age;
std::string surname;
std::string forename;
double salary;
static std::string generate_id() {
static int _next{0};
return "autoid#" + std::to_string(_next++);
}
};
using fus::operator<<;
}
BOOST_FUSION_ADAPT_STRUCT(
client::employee, age, /*id, */ surname, forename, salary)
template <typename It>
struct Parser : qi::grammar<It, client::employee()> {
Parser() : Parser::base_type(start) {
using namespace qi;
name = +graph;
age = uint_ >> eps(_val < 110 && _val > 16);
salary = double_;
auto _id = px::function { std::mem_fn(&client::employee::id) };
auto _gen = px::function { client::employee::generate_id };
start %= skip(space) [
age >> name >> name >> salary >> eps
[ _id(_val) = _gen() ]
];
BOOST_SPIRIT_DEBUG_NODES((start)(name)(age)(salary))
}
private:
qi::rule<It, client::employee()> start;
qi::rule<It, unsigned()> age;
qi::rule<It, std::string()> name;
qi::rule<It, double()> salary;
};
static auto qview(auto f, auto l) {
return std::quoted(
std::string_view(std::addressof(*f), std::distance(f, l)));
}
int main() {
Parser<std::string::const_iterator> p;
std::cout << fus::tuple_delimiter(',');
for (std::string const& input: {
//age surname forename salary
"55 Astley Rick 7232.88",
"23 Black Rebecca 0.00",
"77 Waters Roger 24815.07",
})
{
auto f = begin(input), l = end(input);
client::employee emp;
if (parse(f, l, p, emp)) {
std::cout << "Parsed: " << emp.id << " " << emp << "\n";
} else {
std::cout << "Parse failed\n";
}
if (f != l) {
std::cout << "Remaining unput: " << qview(f,l) << "\n";
}
}
}
Prints
Parsed: autoid#0 (55,Astley,Rick,7232.88)
Parsed: autoid#1 (23,Black,Rebecca,0)
Parsed: autoid#2 (77,Waters,Roger,24815.1)
Alternative: Inline Generation
You'd keep th full adaptation:
BOOST_FUSION_ADAPT_STRUCT(
client::employee, age, id, surname, forename, salary)
And respell the rule using qi::attr()
on the right spot:
auto _gen = px::function { client::employee::generate_id };
start %= skip(space) [
age >> attr(_gen()) >> name >> name >> salary
];
Live On Coliru (omitting rest of unchanged listing)
Printing (again):
Parsed: autoid#0 (55,autoid#0,Astley,Rick,7232.88)
Parsed: autoid#1 (23,autoid#1,Black,Rebecca,0)
Parsed: autoid#2 (77,autoid#2,Waters,Roger,24815.1)
Conclusion
In retrospect, I think the alternative has more appeal.