【问题标题】:Boost::Spirit - Create class by semantic action (maybe by a C++ lambda?)Boost::Spirit - 通过语义操作创建类(也许通过 C++ lambda?)
【发布时间】:2022-01-13 21:36:22
【问题描述】:

我的目标是通过 boost::spirit 解析某些东西,使用语义操作创建一个类(并可能将其放入容器中)。

我设法通过 BOOST_FUSION_ADAPT_STRUCT 宏使用 boost::spirit 创建了一个类。

//The struct
using stringvec = std::string;
struct CVar
{
public:
    stringvec sVariable;
    CVar(stringvec sVariable) : sVariable(sVariable) {}
    CVar() {}
};
//The macro gluing everything
BOOST_FUSION_ADAPT_STRUCT(
    ::CVar,
    (stringvec, sVariable)
)
//The core of the grammar
varName = qi::char_("a-z");
start = *varName;
qi::rule<Iterator, std::string()> varName;
qi::rule<Iterator, std::vector<CVar>() > start;

http://coliru.stacked-crooked.com/a/56dd8325f854a8c9

实际上,我希望省略围绕 上课,但我遇到了问题 http://coliru.stacked-crooked.com/a/3719cee9c7594254我以为我 设法在某个时候将一个类匹配为树的根。这 错误消息看起来,就像适应结构真的想调用 push_back 在某个时候。不知何故,这是有道理的,以防万一 没有要存储的类。

但是,我想通过语义操作调用构造函数。

     start = (varName[qi::_val = boost::phoenix::construct<CVar>(qi::_1)]);

Coliru 通过打印一条非常直接的错误消息(其中 我不能说 VSC++2022).\ 错误:'operator=' 不匹配 (操作数类型是 'std::vector' 和 'CVar') http://coliru.stacked-crooked.com/a/7305e8483ee83b22\

此外,我不想使用太多 boost::bind 或 phoenix 构造。一个普通的 lambda 会很好。 我知道,[] 运算符已经提供了一种 boost::lambda。但是,我不确定如何使用占位符 qi::_val 和 qi::_1。 我应该在 lambda 表达式中捕获它们以执行 qi::_val.push_back(Cvar(qi::_1)) 之类的东西吗?

我研究的一些方向:

我希望这样的事情会起作用:

[&]() { qi::_val.push_back(CVar((std::string)qi::_1)) }

但是我怎样才能真正让它工作呢?

如果不是因为我不想使用 boost 和 push_back,我可以使用 boost::phoenix::construct(qi::_1),不是吗?

我认为这个类似的问题很重要How to build a synthesized argument from a C++11 lambda semantic action in boost spirit? 我仍在寻找如何使用上下文来访问 qi::_val 和 qi::_1。 看起来我正在做某事,但我已经尝试了很多,我可能需要搜索如何阅读 Visual Studio 错误的教程,或者我应该考虑切换编译器。

P.S.:在 VSC++2022 中,我还收到两个警告,其中显示“boost::function_base::functor 未初始化并且 boost::detail::local_counted_base::count_type 没有范围限制。排名枚举类在枚举之前。如何触发它们?

【问题讨论】:

    标签: c++ boost-spirit boost-spirit-qi


    【解决方案1】:

    有很多让我困惑的地方。您说“我想要 XYZ”,没有任何理由说明为什么这样做更可取。此外,您对 不同 方法的声明相同,所以我不知道您更喜欢哪一种(以及为什么)。

    示例代码定义

    using stringvec = std::string;
    

    这令人困惑,因为stringvec 顾名思义是vector&lt;string&gt;,而不是string?现在它看起来更像CVar 是“像一个字符串”,你的属性是vector&lt;CVar&gt;,即像一个字符串向量,而不是std::string。

    总而言之,我可以给你以下提示:

    • 一般来说,避免语义动作。它们对编译器很重,抽象泄漏,选择退出属性兼容性¹,在回溯下产生原子性问题(请参阅Boost Spirit: "Semantic actions are evil"?)

    • 其次,如果您使用语义动作,请意识到更多解析器表达式的原始合成属性是融合序列/容器。

      • 特别是要获得std::string,请使用qi::as_string[]。如果你不使用语义动作,这种属性兼容性/转换确实是自动的¹。
      • 类似地,要将单个项目解析为显式容器,请使用repeat(1)[p]
    • 除了依赖语义操作的所有缺点之外,构造函数的工作方式与您在 phx::construct&lt;&gt; 中展示的一样

    侧面观察:您是否注意到我通过将std::string 替换为char 来减少this previous answer 中的解析器/AST 摩擦?

    应用答案:

    问。实际上,我本来想省略围绕类的向量,但我遇到了问题http://coliru.stacked-crooked.com/a/3719cee9c7594254 我以为我在某个时候设法将类匹配为树的根。错误消息看起来,就像适应结构真的想在某个时候调用 push_back。不知何故,这是有道理的,以防万一没有要存储的类。

    使用as_string进行简化:Live On Coliru

    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <iomanip>
    #include <iostream>
    namespace qi   = boost::spirit::qi;
    using Iterator = std::string::const_iterator;
    
    using stringval = std::string;
    struct CVar { stringval sVariable; };
    BOOST_FUSION_ADAPT_STRUCT(CVar, sVariable)
    
    struct TestGrammar : qi::grammar<Iterator, CVar()> {
        TestGrammar() : TestGrammar::base_type(start) {
            start = qi::as_string[qi::char_("a-z")];
        }
    
    private:
        qi::rule<Iterator, CVar() > start;
    };
    
    void do_test(std::string const& input) {
        CVar output;
    
        static const TestGrammar p;
    
        auto f = input.begin(), l = input.end();
        qi::parse(f, l, p, output);
    
        std::cout << std::quoted(input) << " -> " << std::quoted(output.sVariable) << "\n";
    }
    

    使用repeat(1):Live On Coliru

    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <iomanip>
    #include <iostream>
    namespace qi   = boost::spirit::qi;
    using Iterator = std::string::const_iterator;
    
    using stringval = std::string;
    struct CVar { stringval sVariable; };
    BOOST_FUSION_ADAPT_STRUCT(CVar, sVariable)
    
    struct TestGrammar : qi::grammar<Iterator, CVar()> {
        TestGrammar() : TestGrammar::base_type(start) {
            cvar  = qi::repeat(1)[qi::char_("a-z")];
            start = cvar;
        }
    
      private:
        qi::rule<Iterator, CVar()> start;
        qi::rule<Iterator, std::string()> cvar;
    };
    
    void do_test(std::string const& input) {
        CVar output;
    
        static const TestGrammar p;
    
        auto f = input.begin(), l = input.end();
        qi::parse(f, l, p, output);
    
        std::cout << std::quoted(input) << " -> " << std::quoted(output.sVariable) << "\n";
    }
    

    不使用 ADAPT_STRUCT:minimal change 与:simplified

    #include <boost/spirit/include/qi.hpp>
    #include <iomanip>
    #include <iostream>
    namespace qi   = boost::spirit::qi;
    using Iterator = std::string::const_iterator;
    
    using stringval = std::string;
    struct CVar {
        CVar(std::string v = {}) : sVariable(std::move(v)) {}
        stringval sVariable;
    };
    
    struct TestGrammar : qi::grammar<Iterator, CVar()> {
        TestGrammar() : TestGrammar::base_type(start) {
            start = qi::as_string[qi::lower];
        }
    
      private:
        qi::rule<Iterator, CVar()> start;
    };
    

    使用语义动作(不推荐):4 种实时模式

    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <iomanip>
    #include <iostream>
    namespace qi   = boost::spirit::qi;
    namespace px   = boost::phoenix;
    using Iterator = std::string::const_iterator;
    
    using stringval = std::string;
    struct CVar {
        CVar(std::string v = {}) : sVariable(std::move(v)) {}
        stringval sVariable;
    };
    
    enum mode {
        AS_STRING_CONSTRUCT      = 1,
        DIRECT_ASSIGN            = 2,
        USING_ACTOR              = 3,
        TRANSPARENT_CXX14_LAMBDA = 4,
    };
    
    struct TestGrammar : qi::grammar<Iterator, CVar()> {
        TestGrammar(mode m) : TestGrammar::base_type(start) {
            switch (m) {
                case AS_STRING_CONSTRUCT: {
                        using namespace qi::labels;
                        start = qi::as_string[qi::lower][_val = px::construct<CVar>(_1)];
                        break;
                    }
                case DIRECT_ASSIGN: {
                        // or directly
                        using namespace qi::labels;
                        start = qi::lower[_val = px::construct<std::string>(1ull, _1)];
                        break;
                    }
                case USING_ACTOR: {
                        // or... indeed
                        using namespace qi::labels;
                        px::function as_cvar = [](char var) -> CVar { return {{var}}; };
                        start = qi::lower[_val = as_cvar(_1)];
                        break;
                    }
                    case TRANSPARENT_CXX14_LAMBDA: {
                        // or even more bespoke: (this doesn't require qi::labels or phoenix.hpp)
                        auto propagate = [](auto& attr, auto& ctx) {
                            at_c<0>(ctx.attributes) = {{attr}};
                        };
                        start          = qi::lower[propagate];
                        break;
                    }
            }
        }
    
      private:
        qi::rule<Iterator, CVar()> start;
    };
    
    void do_test(std::string const& input, mode m) {
        CVar output;
    
        const TestGrammar p(m);
    
        auto f = input.begin(), l = input.end();
        qi::parse(f, l, p, output);
    
        std::cout << std::quoted(input) << " -> " << std::quoted(output.sVariable) << "\n";
    }
    
    int main() {
        for (mode m : {AS_STRING_CONSTRUCT, DIRECT_ASSIGN, USING_ACTOR,
                       TRANSPARENT_CXX14_LAMBDA}) {
            std::cout << " ==== mode #" << static_cast<int>(m) << " === \n";
            for (auto s : {"a", "d", "ac"})
                do_test(s, m);
        }
    }
    

    只是为了演示后两种方法如何both do without the constructor 或even without any phoenix support。

    和以前一样,到那时我还是建议使用 C++14 和 Boost Spirit X3:http://coliru.stacked-crooked.com/a/dbd61823354ea8b6 甚至 20 LoC:http://coliru.stacked-crooked.com/a/b26b3db6115c14d4


    ¹有一个非主要的“hack”可以通过定义BOOST_SPIRIT_ACTIONS_ALLOW_ATTR_COMPAT来帮助你

    【讨论】:

    • “一切都很好,只要它被检测为 std::string” - 我认为你需要更准确地思考(或措辞):“一切”? “它是[检测到]”到底是什么 - 是什么? “检测为”是什么意思?
    • 至第二条评论(第一个问题 1):不,构造函数只是一种将默认构造函数和转换构造函数合二为一的优雅方式:coliru.stacked-crooked.com/a/6d60ceadd2d24448。如您所见,那里 is 没有区别。此举显然是附带问题,但我倾向于在我的代码中展示良好实践。
    • 至第三条评论(“第二个”问题 1 [原文如此]):该帖子描述了 相反的(如何强制 repeat() 解析器与 非-容器属性)。当然,如果您认为它描述的是您的情况,您会感到困惑。您正在寻找强制 char 在某些变体中与 std::vector&lt;std::string&gt; 兼容。我的许多口味展示了如何处理事情。只有 1 甚至需要repeat 提供帮助。
    • 提供 8 个不同版本(以及变体)的代码所有都可以工作的重点是你可以弄清楚什么工作和你的工作'重新做不同的。我在答案的第一段中解释了为什么这样做。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-29
    • 1970-01-01
    • 2012-10-12
    • 2013-11-29
    • 2018-10-13
    相关资源
    最近更新 更多