正在将BoostSpirit解析器从Boost::variant转换为std::variation

Transitioning Boost Spirit parser from boost::variant to std::variant

本文关键字:variant 转换 variation std Boost BoostSpirit      更新时间:2024-03-29

我目前正试图将一些代码从使用boost::variant转移到使用std::variation,但遇到了一个我无法解决的问题。下面是一个最小的测试用例:

#include <string>
#include <variant>
#include <boost/spirit/home/x3.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
struct Recurse;
//using Base = boost::variant< // This works
using Base = std::variant<
std::string,
boost::recursive_wrapper<Recurse>>;
struct Recurse
{
int _i;
Base _base = std::string{};
};
BOOST_FUSION_ADAPT_STRUCT(
Recurse,
(int, _i),
(Base, _base)
)
namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;
const x3::rule<class Base_, Base> base = "base";
const auto operand = *x3::char_("a-zA-Z0-9_") | base;
const auto base_def = (x3::int_ >> operand) | operand;
BOOST_SPIRIT_DEFINE(base)
int main()
{
std::string text;
Base result;
x3::phrase_parse(std::begin(text), std::end(text), base, ascii::space, result);
return 0;
}

错误的Wandbox

我认为发生的是,解析器试图将int直接分配给类型为Base的值,但由于int不会直接映射到std::string或boost::recursive_wrapper<>,它变得心烦意乱(我指的是11页的编译器错误(不知何故,boost::variant避免了这个问题。有线索吗?

boost::variant以某种方式避免了错误。

是的。Boost变体具有属性传播支持

此外,boost::variant具有对boost::recursive_wrapper的特殊处理,因此可能是双重禁飞。

这里有一篇关于递归std::variants的好文章https://vittorioromeo.info/index/blog/variants_lambdas_part_2.html

boost::variant出了什么问题?

如果你愿意,你可以写一些转化特征,甚至研究x3::变体——它可能更适合你?

在Coliru上直播

#include <string>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;
struct Recurse;
using Base = x3::variant<
std::string,
x3::forward_ast<Recurse> >;
struct Recurse
{
int _i;
Base _base;
};
BOOST_FUSION_ADAPT_STRUCT(
Recurse,
(int, _i),
(Base, _base)
)
const x3::rule<class Base_, Base> base = "base";
const auto operand = *x3::char_("a-zA-Z0-9_") | base;
const auto base_def = (x3::int_ >> operand) | operand;
BOOST_SPIRIT_DEFINE(base)
int main()
{
std::string text;
Base result;
x3::phrase_parse(std::begin(text), std::end(text), base, ascii::space, result);
return 0;
}

附带说明:没有x3::forward_ast<>std::variant没有帮助,这证实了std::variant只是在x3 中缺乏支持

更新

您可以通过使Base成为具有所需机制的派生结构来解决问题,以向Spirit指示它是一个变体(以及针对哪些类型(。这样你就不必经历特质专业化地狱:

struct Recurse;
struct Base : std::variant<std::string, boost::recursive_wrapper<Recurse> > {
using BaseV = std::variant<std::string, boost::recursive_wrapper<Recurse> >;
using BaseV::BaseV;
using BaseV::operator=;
struct adapted_variant_tag {};
using types = boost::mpl::list<std::string, Recurse>;
};
struct Recurse {
int _i;
Base _base;
};

正如您所看到的,它基本上是相同的,但添加了adapted_variant_tagtypes嵌套类型。

注意通过巧妙地硬编码types序列,我们可以假装巧妙地处理递归包装器。我们很幸运,这足以欺骗系统。

添加一些调试输出和测试用例:

在Coliru上直播

#include <string>
#include <variant>
#include <iostream>
#include <iomanip>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;
namespace { // for debug
template<class T>
std::ostream& operator<<(std::ostream& os, boost::recursive_wrapper<T> const& rw) {
return os << rw.get();
}
template<class... Ts>
std::ostream& operator<<(std::ostream& os, std::variant<Ts...> const& sv) {
std::visit([&os](const auto& v) { os << v; }, sv);
return os;
}
}
struct Recurse;
struct Base : std::variant<std::string, boost::recursive_wrapper<Recurse> > {
using BaseV = std::variant<std::string, boost::recursive_wrapper<Recurse> >;
using BaseV::BaseV;
using BaseV::operator=;
struct adapted_variant_tag {};
using types = boost::mpl::list<std::string, Recurse>;
};
struct Recurse {
int _i;
Base _base;
friend std::ostream& operator<<(std::ostream& os, Recurse const& r) {
return os << "[" << r._i << ", " << r._base << "]";
}
};
BOOST_FUSION_ADAPT_STRUCT(
Recurse,
(int, _i),
(Base, _base)
)
static_assert(x3::traits::is_variant<Base>::value);
const x3::rule<class Base_, Base> base = "base";
const auto operand = *x3::char_("a-zA-Z0-9_") | base;
const auto base_def = (x3::int_ >> operand) | operand;
BOOST_SPIRIT_DEFINE(base)
int main()
{
for (std::string const text : { "yeah8", "32 more" }) {
Base result;
auto f = begin(text), l = end(text);
if (x3::phrase_parse(f, l, base, ascii::space, result)) {
std::cout << "Result: " << result << "n";
} else {
std::cout << "Failedn";
}
if (f!=l) {
std::cout << "Remaining input: " << std::quoted(std::string(f,l)) << "n";
}
}
}

哪个打印

Result: yeah8
Result: [32, more]

更新2:Icing The Cake

以下是使std::variant正常工作所需的特征:

namespace boost::spirit::x3::traits {
template<typename... t>
struct is_variant<std::variant<t...> >
: mpl::true_ {};
template <typename attribute, typename... t>
struct variant_has_substitute_impl<std::variant<t...>, attribute>
{
typedef std::variant<t...> variant_type;
typedef typename mpl::transform<
mpl::list<t...>
, unwrap_recursive<mpl::_1>
>::type types;
typedef typename mpl::end<types>::type end;
typedef typename mpl::find<types, attribute>::type iter_1;
typedef typename
mpl::eval_if<
is_same<iter_1, end>,
mpl::find_if<types, traits::is_substitute<mpl::_1, attribute>>,
mpl::identity<iter_1>
>::type
iter;
typedef mpl::not_<is_same<iter, end>> type;
};

template <typename attribute, typename... t>
struct variant_find_substitute<std::variant<t...>, attribute>
{
typedef std::variant<t...> variant_type;
typedef typename mpl::transform<
mpl::list<t...>
, unwrap_recursive<mpl::_1>
>::type types;
typedef typename mpl::end<types>::type end;
typedef typename mpl::find<types, attribute>::type iter_1;
typedef typename
mpl::eval_if<
is_same<iter_1, end>,
mpl::find_if<types, traits::is_substitute<mpl::_1, attribute> >,
mpl::identity<iter_1>
>::type
iter;
typedef typename
mpl::eval_if<
is_same<iter, end>,
mpl::identity<attribute>,
mpl::deref<iter>
>::type
type;
};
template <typename... t>
struct variant_find_substitute<std::variant<t...>, std::variant<t...> >
: mpl::identity<std::variant<t...> > {};
}

这是一个很大的噪音,但你可以把它放在一个头部的某个地方。

奖金

修复语法:

  • 您可能打算在字符串生产中使用lexeme[]
  • 考虑到没有分隔符,您可能希望字符串的长度最小(+car_,而不是*char_(
  • 您可能不得不重新排序分支,因为字符串生成会吞噬递归规则的整数

以下是我对语法的润色,其中规则与AST非常相似,这通常是有意义的:

namespace Parser {
static_assert(x3::traits::is_variant<Base>::value);
const x3::rule<class Base_, Base> base = "base";
const auto string = x3::lexeme[+x3::char_("a-zA-Z0-9_")];
const auto recurse = x3::int_ >> base;
const auto base_def = recurse | string;
BOOST_SPIRIT_DEFINE(base)
}

简化融合

最后但并非最不重要的是,在C++11时代,你可以推断出适应的融合成员:

BOOST_FUSION_ADAPT_STRUCT(Recurse, _i, _base)

实时完整演示

在Coliru上直播

#include <string>
#include <variant>
#include <iostream>
#include <iomanip>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;
namespace { // for debug
template<class T>
std::ostream& operator<<(std::ostream& os, boost::recursive_wrapper<T> const& rw) {
return os << rw.get();
}
template<class... Ts>
std::ostream& operator<<(std::ostream& os, std::variant<Ts...> const& sv) {
std::visit([&os](const auto& v) { os << v; }, sv);
return os;
}
}
struct Recurse;
using Base = std::variant<
std::string,
boost::recursive_wrapper<Recurse> >;
namespace boost::spirit::x3::traits {
template<typename... T>
struct is_variant<std::variant<T...> >
: mpl::true_ {};
template <typename Attribute, typename... T>
struct variant_has_substitute_impl<std::variant<T...>, Attribute>
{
typedef std::variant<T...> variant_type;
typedef typename mpl::transform<
mpl::list<T...>
, unwrap_recursive<mpl::_1>
>::type types;
typedef typename mpl::end<types>::type end;
typedef typename mpl::find<types, Attribute>::type iter_1;
typedef typename
mpl::eval_if<
is_same<iter_1, end>,
mpl::find_if<types, traits::is_substitute<mpl::_1, Attribute>>,
mpl::identity<iter_1>
>::type
iter;
typedef mpl::not_<is_same<iter, end>> type;
};

template <typename Attribute, typename... T>
struct variant_find_substitute<std::variant<T...>, Attribute>
{
typedef std::variant<T...> variant_type;
typedef typename mpl::transform<
mpl::list<T...>
, unwrap_recursive<mpl::_1>
>::type types;
typedef typename mpl::end<types>::type end;
typedef typename mpl::find<types, Attribute>::type iter_1;
typedef typename
mpl::eval_if<
is_same<iter_1, end>,
mpl::find_if<types, traits::is_substitute<mpl::_1, Attribute> >,
mpl::identity<iter_1>
>::type
iter;
typedef typename
mpl::eval_if<
is_same<iter, end>,
mpl::identity<Attribute>,
mpl::deref<iter>
>::type
type;
};
template <typename... T>
struct variant_find_substitute<std::variant<T...>, std::variant<T...> >
: mpl::identity<std::variant<T...> > {};
}
static_assert(x3::traits::is_variant<Base>{}, "");
struct Recurse
{
int _i;
Base _base;
friend std::ostream& operator<<(std::ostream& os, Recurse const& r) {
return os << "[" << r._i << ", " << r._base << "]";
}
};
BOOST_FUSION_ADAPT_STRUCT(Recurse, _i, _base)
namespace Parser {
static_assert(x3::traits::is_variant<Base>::value);
const x3::rule<class Base_, Base> base = "base";
const auto string = x3::lexeme[+x3::char_("a-zA-Z0-9_")];
const auto recurse = x3::int_ >> base;
const auto base_def = recurse | string;
BOOST_SPIRIT_DEFINE(base)
}
int main()
{
for (std::string const text : { "yeah8", "32 more", "18 766 most" }) {
Base result;
auto f = begin(text), l = end(text);
if (x3::phrase_parse(f, l, Parser::base, ascii::space, result)) {
std::cout << "Result: " << result << "n";
} else {
std::cout << "Failedn";
}
if (f!=l) {
std::cout << "Remaining input: " << std::quoted(std::string(f,l)) << "n";
}
}
}

打印:

Result: yeah8
Result: [32, more]
Result: [18, [766, most]]

(在需要显式访问基类的通用编程中,细微的差异可能会困扰你(