【问题标题】:Transitioning Boost Spirit parser from boost::variant to std::variant将 Boost Spirit 解析器从 boost::variant 转换为 std::variant
【发布时间】:2020-04-23 17:06:26
【问题描述】:

我目前正试图将一些代码从使用 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 避免了这个问题。请问有什么线索吗?

【问题讨论】:

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


    【解决方案1】:

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

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

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

    关于递归std::variants 的好文章在这里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;
    }
    

    旁注:没有x3::forward_ast&lt;&gt;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 序列,我们可以假装巧妙地处理递归包装器。我们很幸运,这足以欺骗系统。

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

    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)
    }
    

    简化融合

    最后但同样重要的是,在 C++11 时代你可以推导出适应的融合成员:

    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]]
    

    ¹(在需要显式访问基类的泛型编程中,细微的差异可能会咬到你)

    【讨论】:

    • "boost::variant 有什么问题?"我试图避免它的主要原因实际上是因为 boost::apply_visitor 会在某种程度上损害编译时间,而 std::visit 显然不会。除此之外,我对 boost::variant 并没有任何问题,尽管我猜一般来说,如果标准库功能可以完成这项工作,最好避免使用 boost。感谢您的建议。
    • 艰难的人群。因此,我将使用一种“hacky”的解决方法进行更新,这种解决方法无法实现完全成熟的特征(这对我来说太重了)
    • 已添加更新 (live demo)。
    • 啊,你确实指出了这一点。 “¹(在需要显式访问基类的泛型编程中,细微的差异可能会咬到你)”
    • @TimAngus 在这种情况下我通常做的是拥有一个auto&amp; as_variant() { return static_cast&lt;base_class&amp;&gt;(*this); } 成员。这胜过使用魔法。是的,我可以向 Spirit 开发人员发送 PR。我很确定他们有兴趣在登陆之前进行一些重构,因为整个过程主要是复制粘贴。此外,recursive_wrapper 技巧对于std::variant 来说似乎有点不合时宜,因为它存在于 Boost Variant 库中。嗯。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-06
    • 1970-01-01
    相关资源
    最近更新 更多