c++ - 将 Boost Spirit 解析器从 boost::variant 转换为 std::variant

标签 c++ boost boost-spirit boost-variant std-variant

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

#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 for the error

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

最佳答案

Somehow boost::variant avoids the error.



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

此外,boost::variantboost::recursive_wrapper的特殊处理所以它可能是双重禁飞。

A good article about recursive std::variants is here https://vittorioromeo.info/index/blog/variants_lambdas_part_2.html



怎么了 boost::variant ?

如果您愿意,您可以编写一些转换特征,甚至查看 x3::variant - 它可能更适合您吗?

Live On 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;
}

Side note: No x3::forward_ast<> does not help with std::variant, confirming that std::variant just lacks support in x3



更新

您可以通过制作 Base 来解决问题。具有所需机制的派生结构,以向 Spirit 指示它是一个变体(以及哪些类型)。这样你就不必经历特质特化 hell :
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序列,我们可以假装巧妙地处理递归包装器。我们很幸运,这足以欺骗系统。

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

Live On 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 << "Failed\n";
        }

        if (f!=l) {
            std::cout << "Remaining input: " << std::quoted(std::string(f,l)) << "\n";
        }

    }
}

哪个打印
Result: yeah8
Result: [32, more]

更新 2:糖霜蛋糕

这是制作 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[]绕弦制作
  • 您可能想要最小长度的字符串(+char_,而不是 *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)
    }
    

    简化 fusion

    最后但并非最不重要的是,在 C++11 时代,您可以推断出适应的 fusion 成员:
    BOOST_FUSION_ADAPT_STRUCT(Recurse, _i, _base)
    

    现场完整演示

    Live On 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 << "Failed\n";
            }
    
            if (f!=l) {
                std::cout << "Remaining input: " << std::quoted(std::string(f,l)) << "\n";
            }
        }
    }
    

    哪个打印:
    Result: yeah8
    Result: [32, more]
    Result: [18, [766, most]]
    

    ¹(在需要显式访问基类的泛型编程中,细微的差异可能会让您感到厌烦)

    关于c++ - 将 Boost Spirit 解析器从 boost::variant 转换为 std::variant,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61392947/

    相关文章:

    c++ - 解析查询与使用 IP 和端口创建端点之间的区别(在 boost asio 中)

    c++ - 获取 boost::gregorian::date 的整数表示

    python - Boost::python 和 Eigen/dense 造成段错误

    c++ - 解析时动态切换解析器

    c++ - 使用Boost.Spirit构建数学表达式计算器时出现编译错误

    c++ - 我可以从手动模板实例化中排除一些方法吗?

    C++ Qt QPixmap 加载总是返回 false

    c++ - 一组高效的 3D 相交算法

    c++ - 是否有处理这种共享资源情况的设计模式或基本的面向对象原则?

    parsing - 我可以从我的船长解析器收集属性吗?