I'm using boost spirit to parse some text. For this I have two grammars. The first one parses a string into a struct, the second one, takes a grammar as template argument and uses it to parse a sequence of data. The second parser should be flexible enough to also handle other grammar return types. Since the original parser is too large to act as a minimal example, I have reduced the code as much as I can, leaving me with something that would not parse anything, but still results in the same compilation errors: (Code on Coliru)
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <vector>
namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;
struct Struct1
{
float f;
};
BOOST_FUSION_ADAPT_STRUCT(
Struct1,
(float, f))
struct Struct2
{
float f;
int i;
};
BOOST_FUSION_ADAPT_STRUCT(
Struct2,
(float, f)
(int, i))
template<typename Iterator,
typename Result>
class ElementParser : public qi::grammar<Iterator, Result(), ascii::space_type>
{
public:
using ValueType = Result;
ElementParser() : ElementParser::base_type(element) {}
private:
qi::rule<Iterator, Result(), ascii::space_type> element;
};
template<typename Iterator,
typename ElementParser,
typename Element = typename ElementParser::ValueType>
class SP : public qi::grammar<Iterator, std::vector<Element>(), ascii::space_type>
{
public:
SP()
: SP::base_type(sequence)
{
sequence %= simpleVector % ',';
// The simpleVector hack is really needed because of some other parsing
// stuff, that is going on, but has been left out here.
simpleVector %= qi::repeat(1)[simple];
}
private:
using Rule = qi::rule<Iterator, std::vector<Element>(), ascii::space_type>;
Rule sequence;
Rule simpleVector;
ElementParser simple;
};
void sequenceTest()
{
using Iterator = std::string::const_iterator;
SP<Iterator, qi::uint_parser<>, std::size_t> uintParser; // OK
SP<Iterator, ElementParser<Iterator, float>> floatParser; // OK
SP<Iterator, ElementParser<Iterator, std::vector<float>>> vectorParser; // OK
// error: invalid static_cast from type 'const std::vector<Struct1, std::allocator<Struct1> >' to type 'element_type' {aka 'float'}
SP<Iterator, ElementParser<Iterator, Struct1>> struct1Parser;
// error: no matching function for call to 'Struct2::Struct2(const std::vector<Struct2, std::allocator<Struct2> >&)'
SP<Iterator, ElementParser<Iterator, Struct2>> struct2Parser;
}
As long, as I am using simple types or vectors as return types of the ElementParser
, everything is working fine, but as soon as I'm parsing into a struct (which in itself is working fine), the sequence parser SP
seems to try some stange assignments. Why do the struct versions result in compilation errors?
Here is an even shorter example, demonstrating the same problem (compiler explorer):
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/spirit/include/qi.hpp>
#include <vector>
#include <tuple>
namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;
void test()
{
using Iterator = std::string::const_iterator;
// OK
qi::rule<Iterator, std::vector<int>(), ascii::space_type> vecI_src;
qi::rule<Iterator, std::vector<int>(), ascii::space_type> vecI_dst = *vecI_src;
// error: no matching function for call to 'std::tuple<int, float>::tuple(const std::vector<std::tuple<int, float> >&)'
qi::rule<Iterator, std::vector<std::tuple<int, float>>(), ascii::space_type> vecT_src;
qi::rule<Iterator, std::vector<std::tuple<int, float>>(), ascii::space_type> vecT_dst = *vecT_src;
}
I think, the problem is, that vectors and tuples are handles quite similarly in the underlying boost::fusion
library, so when it comes to flattening the vector, boost::fusion
overshoots the goal and assignment fails. (Possibly by some kind of SFINAE mechanism.) Now, that flattening the vector does not work, the right-hand-side tuple
parser's synthesized attribute is of type vector<vector<tuple<int, float>>>
, as opposed to the expected vector<tuple<int, float>>
.
Knowing this, the (not very pretty) solution I've found (for the original example) is to manually create assignment function overloads for both expected forms:
static
void flattenAndAppend(std::vector<Element>& into,
std::vector<std::vector<Element>> const& vector)
{
for(auto const& subvector: vector)
{
into.insert(into.end(), subvector.begin(), subvector.end());
}
}
static
void flattenAndAppend(std::vector<Element>& into,
std::vector<Element> const& vector)
{
into.insert(into.end(), vector.begin(), vector.end());
}
and call these in a semantic action via a boost::phoenix
function:
ph::function append = [](auto& into,
auto const& a1)
{
flattenAndAppend(into, a1);
};
sequence = (simpleVector % ',')[append(qi::_val, ql::_1)];
Here is the whole working example (compiler explorer):
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <vector>
#include <tuple>
namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;
namespace ql = qi::labels;
namespace ph = boost::phoenix;
struct Struct1
{
float f;
};
BOOST_FUSION_ADAPT_STRUCT(
Struct1,
(float, f))
struct Struct2
{
float f;
int i;
};
BOOST_FUSION_ADAPT_STRUCT(
Struct2,
(float, f)
(int, i))
template<typename Iterator,
typename Result>
class ElementParser : public qi::grammar<Iterator, Result(), ascii::space_type>
{
public:
using ValueType = Result;
ElementParser() : ElementParser::base_type(element) {}
private:
qi::rule<Iterator, Result(), ascii::space_type> element;
};
template<typename Iterator>
class Struct2Tuple : public qi::grammar<Iterator, std::tuple<float, int>(), ascii::space_type>
{
public:
using ValueType = std::tuple<float, int>;
Struct2Tuple() : Struct2Tuple::base_type(tupleElement)
{
ph::function convert = [](auto const& s,
auto& t)
{
t = std::make_tuple(s.f, s.i);
};
tupleElement = structElement[convert(ql::_1, qi::_val)];
}
private:
qi::rule<Iterator, ValueType(), ascii::space_type> tupleElement;
ElementParser<Iterator, Struct2> structElement;
};
template<typename Iterator,
typename ElementParser,
typename Element = typename ElementParser::ValueType>
class SP : public qi::grammar<Iterator, std::vector<Element>(), ascii::space_type>
{
private:
static
void flattenAndAppend(std::vector<Element>& into,
std::vector<std::vector<Element>> const& vector)
{
for(auto const& subvector: vector)
{
into.insert(into.end(), subvector.begin(), subvector.end());
}
}
static
void flattenAndAppend(std::vector<Element>& into,
std::vector<Element> const& vector)
{
into.insert(into.end(), vector.begin(), vector.end());
}
public:
SP()
: SP::base_type(sequence)
{
ph::function append = [](auto& into,
auto const& a1)
{
flattenAndAppend(into, a1);
};
sequence = (simpleVector % ',')[append(qi::_val, ql::_1)];
simpleVector = qi::repeat(1)[simple];
}
private:
using Rule = qi::rule<Iterator, std::vector<Element>(), ascii::space_type>;
Rule sequence;
Rule simpleVector;
ElementParser simple;
};
void sequenceTest()
{
using Iterator = std::string::const_iterator;
SP<Iterator, qi::uint_parser<>, std::size_t> uintParser; // OK
SP<Iterator, ElementParser<Iterator, float>> floatParser; // OK
SP<Iterator, ElementParser<Iterator, std::vector<float>>> vectorParser; // OK
SP<Iterator, Struct2Tuple<Iterator>> struct2tupleParser; // OK.
SP<Iterator, ElementParser<Iterator, std::tuple<float, float>>> tupleParser; // now OK
SP<Iterator, ElementParser<Iterator, Struct1>> struct1Parser; // now OK
SP<Iterator, ElementParser<Iterator, Struct2>> struct2Parser; // now OK
}