将解析到结构中的 boost-spirit 语法嵌入到另一个语法中会产生编译错误
Embedding a boost-spirit grammar parsing into a struct into another grammar gives compilation errors
我正在使用 boost spirit 来解析一些文本。为此,我有两个语法。第一个将字符串解析为结构,第二个将语法作为模板参数并使用它来解析数据序列。第二个解析器应该足够灵活,也可以处理其他语法 return 类型。由于原始解析器太大而无法作为最小示例,我已尽可能减少代码,留下一些无法解析任何内容但仍会导致相同编译错误的内容:(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;
}
只要我使用简单类型或向量作为 ElementParser
的 return 类型,一切都运行良好,但是一旦我解析为结构(在本身工作正常),序列解析器 SP
似乎尝试了一些奇怪的分配。为什么结构版本会导致编译错误?
我认为您正在绕过古老的单元素序列兼容性规则。尤其是 Struct1,它确实被改编为单元素序列。
但是,在您的代码中,我可以通过删除不必要的 repeat(1)[]
装置轻松使其工作:
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;
struct Struct1 { float f; };
struct Struct2 { float f; int i; };
BOOST_FUSION_ADAPT_STRUCT(Struct1, f)
BOOST_FUSION_ADAPT_STRUCT(Struct2, f, 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 SequenceParser
: public qi::grammar<Iterator, std::vector<Element>(), ascii::space_type> {
public:
SequenceParser() : SequenceParser::base_type(sequence) {
sequence = simple % ',';
}
private:
qi::rule<Iterator, std::vector<Element>(), ascii::space_type> sequence;
ElementParser simple;
};
void sequenceTest() {
using It = std::string::const_iterator;
SequenceParser<It, qi::uint_parser<>, std::size_t> uintParser; // OK
SequenceParser<It, ElementParser<It, float>> floatParser; // OK
SequenceParser<It, ElementParser<It, std::vector<float>>>
vectorParser; // OK
SequenceParser<It, ElementParser<It, Struct1>> struct1Parser;
SequenceParser<It, ElementParser<It, Struct2>> struct2Parser;
}
int main() {
sequenceTest();
}
奖励:JustParseIt
魔法函数
请注意,在某种程度上,您似乎在重新审视图书馆设计。看看 qi::auto_
.
添加来自这里的想法:
例如,通过专门化特征来制作逗号分隔的序列解析器:
template <typename... T>
struct create_parser<std::vector<T...>> : comma_separated_sequence {};
struct comma_separated_sequence {
using type = decltype(qi::copy(qi::auto_ % ','));
static type call() { return qi::copy(qi::auto_ % ','); }
};
现在你可以实现一个与/the world/一起工作的JustParseIt
函数:
bool JustParseIt(std::string_view input, auto& val) {
return qi::phrase_parse(input.begin(), input.end(), qi::auto_, qi::space, val);
}
你会惊讶地看到它解析的内容:
#include <boost/fusion/include/adapted.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/include/qi.hpp>
#include <fmt/ranges.h>
#include <fmt/ostream.h>
namespace qi = boost::spirit::qi;
namespace MyLib {
struct Struct1 { float f; };
struct Struct2 { float f; int i; };
using boost::fusion::operator<<;
}
BOOST_FUSION_ADAPT_STRUCT(MyLib::Struct1, f)
BOOST_FUSION_ADAPT_STRUCT(MyLib::Struct2, f, i)
namespace {
struct comma_separated_sequence {
using type = decltype(qi::copy(qi::auto_ % ','));
static type call() { return qi::copy(qi::auto_ % ','); }
};
}
namespace boost::spirit::traits {
template <typename... T>
struct create_parser<std::list<T...>> : comma_separated_sequence {};
template <typename... T>
struct create_parser<std::vector<T...>> : comma_separated_sequence {};
}
bool JustParseIt(std::string_view input, auto& val) {
#ifdef BOOST_SPIRIT_DEBUG
using It = decltype(input.begin());
using Skipper = qi::space_type;
using Attr = std::decay_t<decltype(val)>;
static qi::rule<It, Attr(), Skipper> parser = qi::auto_;
BOOST_SPIRIT_DEBUG_NODE(parser);
return qi::phrase_parse(input.begin(), input.end(), parser, qi::space, val);
#else
return qi::phrase_parse(input.begin(), input.end(), qi::auto_, qi::space, val);
#endif
}
int main() {
using namespace MyLib;
std::cerr << std::boolalpha; // make debug easier to read
float f;
JustParseIt("3.1415", f);
uint64_t u;
JustParseIt("00897823", u);
Struct1 s1;
JustParseIt("3.1415", s1);
Struct2 s2;
JustParseIt("3.1415 00897823", s2);
std::list<float> floats;;
JustParseIt("1.2,3.4", floats);
std::list<Struct1> list1;
JustParseIt("1.2", list1);
JustParseIt("1.2, -inf, 9e10, NaN", list1);
std::vector<boost::variant<Struct2, bool> > variants;
JustParseIt("true, 9e10 123, NaN 234, false, false", variants);
std::vector<Struct2> vec2;
JustParseIt("9e10 123, NaN 234", vec2);
// this is pushing it - for lack of structurual syntax
std::vector<std::tuple<bool, Struct1, std::vector<Struct2>>> insane;
JustParseIt("true 3.14 1e1 1, 2e2 2, 3e3 3, false +inf 4e4 4", insane);
fmt::print("float f: {}\n"
"uint64_t u: {}\n"
"std::list<float> floats: {}\n"
"std::list<Struct1> list1: {}\n"
"std::vector<Struct2> vec2: {}\n"
"Struct1 s1: {}\n"
"Struct2 s2: {}\n"
"std::vector<boost::variant<Struct2, bool> > variants: {}\n"
"std::vector<std::tuple<bool, Struct1, std::vector<Struct2>>> "
"insane: {}\n",
f, u, floats, list1, vec2, s1, s2, variants, insane);
}
版画
float f: 3.1415
uint64_t u: 897823
std::list<float> floats: {1.2, 3.4}
std::list<Struct1> list1: {(1.2), (1.2), (-inf), (9e+10), (nan)}
std::vector<Struct2> vec2: {(9e+10 123), (nan 234)}
Struct1 s1: (3.1415)
Struct2 s2: (3.1415 897823)
std::vector<boost::variant<Struct2, bool> > variants: {1, (9e+10 123), (nan 234), 0, 0}
std::vector<std::tuple<bool, Struct1, std::vector<Struct2>>> insane: {(true, (3.14), {(10 1), (200 2), (3000 3)}), (false, (inf), {(40000 4)})}
Note that you can define BOOST_SPIRIT_DEBUG
to get parser debugging to stderr, like e.g.
<parser>
<try>true, 9e10 123, NaN </try>
<success></success>
<attributes>[[true, [9e+10, 123], [nan, 234], false, false]]</attributes>
</parser>
这是一个更短的例子,演示了同样的问题 (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;
}
我认为,问题在于,向量和元组在基础 boost::fusion
库中的处理方式非常相似,因此当涉及到扁平化向量时,boost::fusion
超出了目标并且赋值失败. (可能是通过某种 SFINAE 机制。)现在,扁平化向量不起作用,右侧 tuple
解析器的合成属性是 vector<vector<tuple<int, float>>>
类型,而不是预期的 vector<tuple<int, float>>
.
知道这一点后,我找到的(不是很漂亮的)解决方案(对于原始示例)是为两种预期形式手动创建赋值函数重载:
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());
}
并通过 boost::phoenix
函数在语义操作中调用它们:
ph::function append = [](auto& into,
auto const& a1)
{
flattenAndAppend(into, a1);
};
sequence = (simpleVector % ',')[append(qi::_val, ql::_1)];
这是整个工作示例 (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
}
我正在使用 boost spirit 来解析一些文本。为此,我有两个语法。第一个将字符串解析为结构,第二个将语法作为模板参数并使用它来解析数据序列。第二个解析器应该足够灵活,也可以处理其他语法 return 类型。由于原始解析器太大而无法作为最小示例,我已尽可能减少代码,留下一些无法解析任何内容但仍会导致相同编译错误的内容:(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;
}
只要我使用简单类型或向量作为 ElementParser
的 return 类型,一切都运行良好,但是一旦我解析为结构(在本身工作正常),序列解析器 SP
似乎尝试了一些奇怪的分配。为什么结构版本会导致编译错误?
我认为您正在绕过古老的单元素序列兼容性规则。尤其是 Struct1,它确实被改编为单元素序列。
但是,在您的代码中,我可以通过删除不必要的 repeat(1)[]
装置轻松使其工作:
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;
struct Struct1 { float f; };
struct Struct2 { float f; int i; };
BOOST_FUSION_ADAPT_STRUCT(Struct1, f)
BOOST_FUSION_ADAPT_STRUCT(Struct2, f, 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 SequenceParser
: public qi::grammar<Iterator, std::vector<Element>(), ascii::space_type> {
public:
SequenceParser() : SequenceParser::base_type(sequence) {
sequence = simple % ',';
}
private:
qi::rule<Iterator, std::vector<Element>(), ascii::space_type> sequence;
ElementParser simple;
};
void sequenceTest() {
using It = std::string::const_iterator;
SequenceParser<It, qi::uint_parser<>, std::size_t> uintParser; // OK
SequenceParser<It, ElementParser<It, float>> floatParser; // OK
SequenceParser<It, ElementParser<It, std::vector<float>>>
vectorParser; // OK
SequenceParser<It, ElementParser<It, Struct1>> struct1Parser;
SequenceParser<It, ElementParser<It, Struct2>> struct2Parser;
}
int main() {
sequenceTest();
}
奖励:JustParseIt
魔法函数
请注意,在某种程度上,您似乎在重新审视图书馆设计。看看 qi::auto_
.
添加来自这里的想法:
例如,通过专门化特征来制作逗号分隔的序列解析器:
template <typename... T>
struct create_parser<std::vector<T...>> : comma_separated_sequence {};
struct comma_separated_sequence {
using type = decltype(qi::copy(qi::auto_ % ','));
static type call() { return qi::copy(qi::auto_ % ','); }
};
现在你可以实现一个与/the world/一起工作的JustParseIt
函数:
bool JustParseIt(std::string_view input, auto& val) {
return qi::phrase_parse(input.begin(), input.end(), qi::auto_, qi::space, val);
}
你会惊讶地看到它解析的内容:
#include <boost/fusion/include/adapted.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/include/qi.hpp>
#include <fmt/ranges.h>
#include <fmt/ostream.h>
namespace qi = boost::spirit::qi;
namespace MyLib {
struct Struct1 { float f; };
struct Struct2 { float f; int i; };
using boost::fusion::operator<<;
}
BOOST_FUSION_ADAPT_STRUCT(MyLib::Struct1, f)
BOOST_FUSION_ADAPT_STRUCT(MyLib::Struct2, f, i)
namespace {
struct comma_separated_sequence {
using type = decltype(qi::copy(qi::auto_ % ','));
static type call() { return qi::copy(qi::auto_ % ','); }
};
}
namespace boost::spirit::traits {
template <typename... T>
struct create_parser<std::list<T...>> : comma_separated_sequence {};
template <typename... T>
struct create_parser<std::vector<T...>> : comma_separated_sequence {};
}
bool JustParseIt(std::string_view input, auto& val) {
#ifdef BOOST_SPIRIT_DEBUG
using It = decltype(input.begin());
using Skipper = qi::space_type;
using Attr = std::decay_t<decltype(val)>;
static qi::rule<It, Attr(), Skipper> parser = qi::auto_;
BOOST_SPIRIT_DEBUG_NODE(parser);
return qi::phrase_parse(input.begin(), input.end(), parser, qi::space, val);
#else
return qi::phrase_parse(input.begin(), input.end(), qi::auto_, qi::space, val);
#endif
}
int main() {
using namespace MyLib;
std::cerr << std::boolalpha; // make debug easier to read
float f;
JustParseIt("3.1415", f);
uint64_t u;
JustParseIt("00897823", u);
Struct1 s1;
JustParseIt("3.1415", s1);
Struct2 s2;
JustParseIt("3.1415 00897823", s2);
std::list<float> floats;;
JustParseIt("1.2,3.4", floats);
std::list<Struct1> list1;
JustParseIt("1.2", list1);
JustParseIt("1.2, -inf, 9e10, NaN", list1);
std::vector<boost::variant<Struct2, bool> > variants;
JustParseIt("true, 9e10 123, NaN 234, false, false", variants);
std::vector<Struct2> vec2;
JustParseIt("9e10 123, NaN 234", vec2);
// this is pushing it - for lack of structurual syntax
std::vector<std::tuple<bool, Struct1, std::vector<Struct2>>> insane;
JustParseIt("true 3.14 1e1 1, 2e2 2, 3e3 3, false +inf 4e4 4", insane);
fmt::print("float f: {}\n"
"uint64_t u: {}\n"
"std::list<float> floats: {}\n"
"std::list<Struct1> list1: {}\n"
"std::vector<Struct2> vec2: {}\n"
"Struct1 s1: {}\n"
"Struct2 s2: {}\n"
"std::vector<boost::variant<Struct2, bool> > variants: {}\n"
"std::vector<std::tuple<bool, Struct1, std::vector<Struct2>>> "
"insane: {}\n",
f, u, floats, list1, vec2, s1, s2, variants, insane);
}
版画
float f: 3.1415
uint64_t u: 897823
std::list<float> floats: {1.2, 3.4}
std::list<Struct1> list1: {(1.2), (1.2), (-inf), (9e+10), (nan)}
std::vector<Struct2> vec2: {(9e+10 123), (nan 234)}
Struct1 s1: (3.1415)
Struct2 s2: (3.1415 897823)
std::vector<boost::variant<Struct2, bool> > variants: {1, (9e+10 123), (nan 234), 0, 0}
std::vector<std::tuple<bool, Struct1, std::vector<Struct2>>> insane: {(true, (3.14), {(10 1), (200 2), (3000 3)}), (false, (inf), {(40000 4)})}
Note that you can define
BOOST_SPIRIT_DEBUG
to get parser debugging to stderr, like e.g.<parser> <try>true, 9e10 123, NaN </try> <success></success> <attributes>[[true, [9e+10, 123], [nan, 234], false, false]]</attributes> </parser>
这是一个更短的例子,演示了同样的问题 (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;
}
我认为,问题在于,向量和元组在基础 boost::fusion
库中的处理方式非常相似,因此当涉及到扁平化向量时,boost::fusion
超出了目标并且赋值失败. (可能是通过某种 SFINAE 机制。)现在,扁平化向量不起作用,右侧 tuple
解析器的合成属性是 vector<vector<tuple<int, float>>>
类型,而不是预期的 vector<tuple<int, float>>
.
知道这一点后,我找到的(不是很漂亮的)解决方案(对于原始示例)是为两种预期形式手动创建赋值函数重载:
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());
}
并通过 boost::phoenix
函数在语义操作中调用它们:
ph::function append = [](auto& into,
auto const& a1)
{
flattenAndAppend(into, a1);
};
sequence = (simpleVector % ',')[append(qi::_val, ql::_1)];
这是整个工作示例 (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
}