【问题标题】:Boost Karma generator for composition of classes用于组合类的 Boost Karma 生成器
【发布时间】:2017-10-19 21:10:59
【问题描述】:

我有以下类图:

有一些未使用的类,例如 BinaryOperator,但我的真实代码需要它们,所以我想在示例中也保留它们。

我想使用boost::karma 来获得它的JSON 表示。 JSON 应该如下所示:

{
  "name": "Plus",
  "type": "Function",
  "arguments": [
    {
      "name": "IntegerValue",
      "type": "Value",
      "value": "4"
    },
    {
      "name": "Plus",
      "type": "Function",
      "arguments": [
        {
          "name": "IntegerValue",
          "type": "Value",
          "value": "5"
        },
        {
          "name": "IntegerValue",
          "type": "Value",
          "value": "6"
        }
      ]
    }
  ]
}

由于这是一个简单的示例,我想在我的类中使用 BOOST_FUSION_ADAPT_ADT 宏来模块化生成器。

我是 Karma 的新手,我已经阅读了 boost 网站上的教程,但我不明白如何解决我的问题。我找不到关于那个宏的好教程。

我不想使用现有的 JSON 库,因为一开始我想学习 Karma,其次 JSON 只是一个例子,我需要以多种格式导出我的表达式,我可以通过简单的方式完成更改生成器,而我的类使用 BOOST_FUSION_ADAPT_ADT 的代码应该是相同的。

您可以找到创建示例表达式的代码。为了解决我的问题,我需要从哪里开始?

#include <boost/lexical_cast.hpp>
#include <iostream>
#include <vector>

class Expression {
public:

  virtual std::string getName() const = 0;
};

class Value : public Expression {
public:

  virtual std::string getValue() const = 0;
};

class IntegerValue : public Value {
public:

  IntegerValue(int value) : m_value(value) {}
  virtual std::string getName() const override { return "IntegerValue"; }
  virtual std::string getValue() const override { return boost::lexical_cast<std::string>(m_value); }

private:

  int m_value;
};

class Function : public Expression {
public:

  void addArgument(Expression* expression) { m_arguments.push_back(expression); }
  virtual std::string getName() const override { return m_name; }

protected:

  std::vector<Expression*> m_arguments;
  std::string m_name;
};

class Plus : public Function {
public:

  Plus() : Function() { m_name = "Plus"; }
};

///////////////////////////////////////////////////////////////////////////////

int main(int argc, char **argv) {

  // Build expression 4 + 5 + 6 as 4 + (5 + 6)
  Function* plus1 = new Plus();
  Function* plus2 = new Plus();
  Value* iv4   = new IntegerValue(4);
  Value* iv5   = new IntegerValue(5);
  Value* iv6   = new IntegerValue(6);
  plus2->addArgument(iv5);
  plus2->addArgument(iv6);
  plus1->addArgument(iv4);
  plus1->addArgument(plus2);

  // Generate json string here, but how?

  return 0;
}

【问题讨论】:

    标签: c++ json boost boost-spirit


    【解决方案1】:

    我建议不要使用 Karma 来生成 JSON。我建议强烈反对 ADAPT_ADT(它很容易出现非常微妙的 UB 错误,这意味着您正在尝试调整不是为它设计的东西。只是说不)。

    这是我的看法。让我们走大路,尽可能不打扰。这意味着

    • 我们不能只重载 operator&lt;&lt; 来打印 json(因为您可能希望自然地打印表达式)
    • 这也意味着负责生成 JSON 的函数不会

      • 不得不为 json 实现细节烦恼
      • 不得不为漂亮的格式而烦恼
    • 最后,我不想使用任何特定于 JSON 的内容侵入表达式树。最多可以接受的是不透明朋友声明。


    一个简单的 JSON 工具:

    这可能是最简单的 JSON 表示,但它完成了所需的子集并做出了许多明智的选择(例如支持重复属性、保留属性顺序):

    #include <boost/variant.hpp>
    namespace json {
        // adhoc JSON rep
        struct Null {};
        using String = std::string;
    
        using Value = boost::make_recursive_variant<
            Null,
            String,
            std::vector<boost::recursive_variant_>,
            std::vector<std::pair<String, boost::recursive_variant_> >
        >::type;
    
        using Property = std::pair<String, Value>;
        using Object = std::vector<Property>;
        using Array = std::vector<Value>;
    }
    

    就是这样。这是功能齐全的。让我们证明一下


    漂亮的打印 JSON

    就像表达式树本身一样,我们不要硬连线它,而是创建一个漂亮的打印 IO 操纵器:

    #include <iomanip>
    namespace json {
    
        // pretty print it
        struct pretty_io {
            using result_type = void;
    
            template <typename Ref>
            struct manip {
                Ref ref;
                friend std::ostream& operator<<(std::ostream& os, manip const& m) {
                    pretty_io{os,""}(m.ref);
                    return os;
                }
            };
    
            std::ostream& _os;
            std::string _indent;
    
            void operator()(Value const& v) const {
                boost::apply_visitor(*this, v);
            }
            void operator()(Null) const {
                _os << "null";
            }
            void operator()(String const& s) const {
                _os << std::quoted(s);
            }
            void operator()(Property const& p) const {
                _os << '\n' << _indent; operator()(p.first);
                _os << ": ";            operator()(p.second);
            }
            void operator()(Object const& o) const {
                pretty_io nested{_os, _indent+"  "};
                _os << "{";
                bool first = true;
                for (auto& p : o) { first||_os << ","; nested(p); first = false; }
                _os << "\n" << _indent << "}";
            }
            void operator()(Array const& o) const {
                pretty_io nested{_os, _indent+"  "};
                _os << "[\n" << _indent << "  ";
                bool first = true;
                for (auto& p : o) { first||_os << ",\n" << _indent << "  "; nested(p); first = false; }
                _os << "\n" << _indent << "]";
            }
        };
    
        Value to_json(Value const& v) { return v; }
    
        template <typename T, typename V = decltype(to_json(std::declval<T const&>()))>
        pretty_io::manip<V> pretty(T const& v) { return {to_json(v)}; }
    }
    

    to_json 被称为方便的启用 ADL 的扩展点,您现在已经可以使用它了:

    std::cout << json::pretty("hello world"); // prints as a JSON String
    

    连接起来

    完成以下工作:

    std::cout << json::pretty(plus1);
    

    我们需要的只是适当的to_json 重载。我们可以把它全部记在里面,但我们最终可能需要“加好友”一个名为to_json 的函数,更糟糕的是,从json 命名空间(至少json::Value)转发声明类型。这太打扰了。所以,让我们添加另一个微小的间接:

    auto to_json(Expression const* expression) {
        return serialization::call(expression);
    }
    

    诀窍是将 JSON 内容隐藏在一个不透明的结构中,然后我们可以与它成为朋友:struct serialization。其余的很简单:

    struct serialization {
        static json::Value call(Expression const* e) {
            if (auto* f = dynamic_cast<Function const*>(e)) {
                json::Array args;
                for (auto& a : f->m_arguments)
                    args.push_back(call(a));
                return json::Object {
                    { "name", f->getName() },
                    { "type", "Function" },
                    { "arguments", args },
                };
            }
    
            if (auto* v = dynamic_cast<Value const*>(e)) {
                return json::Object {
                    { "name", v->getName() },
                    { "type", "Value" },
                    { "value", v->getValue() },
                };
            }
    
            return {}; // Null in case we didn't implement a node type
        }
    };
    

    完整演示

    Live On Coliru

    #include <boost/lexical_cast.hpp>
    #include <iostream>
    #include <iomanip>
    #include <vector>
    
    struct Expression {
        virtual std::string getName() const = 0;
    };
    
    struct Value : Expression {
        virtual std::string getValue() const = 0;
    };
    
    struct IntegerValue : Value {
        IntegerValue(int value) : m_value(value) {}
        virtual std::string getName() const override { return "IntegerValue"; }
        virtual std::string getValue() const override { return boost::lexical_cast<std::string>(m_value); }
    
      private:
        int m_value;
    };
    
    struct Function : Expression {
        void addArgument(Expression *expression) { m_arguments.push_back(expression); }
        virtual std::string getName() const override { return m_name; }
    
      protected:
        std::vector<Expression *> m_arguments;
        std::string m_name;
    
        friend struct serialization;
    };
    
    struct Plus : Function {
        Plus() : Function() { m_name = "Plus"; }
    };
    
    ///////////////////////////////////////////////////////////////////////////////
    // A simple JSON facility
    #include <boost/variant.hpp>
    namespace json {
        // adhoc JSON rep
        struct Null {};
        using String = std::string;
    
        using Value = boost::make_recursive_variant<
            Null,
            String,
            std::vector<boost::recursive_variant_>,
            std::vector<std::pair<String, boost::recursive_variant_> >
        >::type;
    
        using Property = std::pair<String, Value>;
        using Object = std::vector<Property>;
        using Array = std::vector<Value>;
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // Pretty Print manipulator
    #include <iomanip>
    namespace json {
    
        // pretty print it
        struct pretty_io {
            using result_type = void;
    
            template <typename Ref>
            struct manip {
                Ref ref;
                friend std::ostream& operator<<(std::ostream& os, manip const& m) {
                    pretty_io{os,""}(m.ref);
                    return os;
                }
            };
    
            std::ostream& _os;
            std::string _indent;
    
            void operator()(Value const& v) const {
                boost::apply_visitor(*this, v);
            }
            void operator()(Null) const {
                _os << "null";
            }
            void operator()(String const& s) const {
                _os << std::quoted(s);
            }
            void operator()(Property const& p) const {
                _os << '\n' << _indent; operator()(p.first);
                _os << ": ";            operator()(p.second);
            }
            void operator()(Object const& o) const {
                pretty_io nested{_os, _indent+"  "};
                _os << "{";
                bool first = true;
                for (auto& p : o) { first||_os << ","; nested(p); first = false; }
                _os << "\n" << _indent << "}";
            }
            void operator()(Array const& o) const {
                pretty_io nested{_os, _indent+"  "};
                _os << "[\n" << _indent << "  ";
                bool first = true;
                for (auto& p : o) { first||_os << ",\n" << _indent << "  "; nested(p); first = false; }
                _os << "\n" << _indent << "]";
            }
        };
    
        Value to_json(Value const& v) { return v; }
    
        template <typename T, typename V = decltype(to_json(std::declval<T const&>()))>
        pretty_io::manip<V> pretty(T const& v) { return {to_json(v)}; }
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // Expression -> JSON
    struct serialization {
        static json::Value call(Expression const* e) {
            if (auto* f = dynamic_cast<Function const*>(e)) {
                json::Array args;
                for (auto& a : f->m_arguments)
                    args.push_back(call(a));
                return json::Object {
                    { "name", f->getName() },
                    { "type", "Function" },
                    { "arguments", args },
                };
            }
    
            if (auto* v = dynamic_cast<Value const*>(e)) {
                return json::Object {
                    { "name", v->getName() },
                    { "type", "Value" },
                    { "value", v->getValue() },
                };
            }
    
            return {};
        }
    };
    
    auto to_json(Expression const* expression) {
        return serialization::call(expression);
    }
    
    int main() {
        // Build expression 4 + 5 + 6 as 4 + (5 + 6)
        Function *plus1 = new Plus();
        Function *plus2 = new Plus();
        Value *iv4 = new IntegerValue(4);
        Value *iv5 = new IntegerValue(5);
        Value *iv6 = new IntegerValue(6);
        plus2->addArgument(iv5);
        plus2->addArgument(iv6);
        plus1->addArgument(iv4);
        plus1->addArgument(plus2);
    
        // Generate json string here, but how?
    
        std::cout << json::pretty(plus1);
    }
    

    您的问题的输出是完美的:

    {
      "name": "Plus",
      "type": "Function",
      "arguments": [
        {
          "name": "IntegerValue",
          "type": "Value",
          "value": "4"
        },
        {
          "name": "Plus",
          "type": "Function",
          "arguments": [
            {
              "name": "IntegerValue",
              "type": "Value",
              "value": "5"
            },
            {
              "name": "IntegerValue",
              "type": "Value",
              "value": "6"
            }
          ]
        }
      ]
    }
    

    【讨论】:

    • 附言。我忘了提到——显然——你应该考虑一个 JSON 库,但这种方法仍然有效。如果你很聪明,你可以只使用与后端/格式无关的访问器来为 serialization 结构分配任务,并将所有后端分离到不同的 TU。我将把它作为众所周知的练习留给读者。
    • 谢谢,事实是 json 只是我必须使用的众多格式之一,有些格式是专有的并且没有库,所以我想为所有人使用统一的方式。我决定使用 json 来回答这个问题,因为社区知道的不仅仅是 asciimath 或我们创建的其他格式。
    【解决方案2】:

    谢谢,事实是 json 只是我必须使用的众多格式之一,有些格式是专有的并且没有库,所以我想为所有人使用统一的方式。我决定使用 json 来解决这个问题,因为社区知道的不仅仅是 asciimath 或我们创建的其他格式 – Jepessen 9 hours ago

    这对我的建议没有任何改变。如果有的话,它确实强调了您不希望施加任意限制。

    Karma 的问题

    • Karma 是用于静态生成器的“内联”DSL。它们适用于静态类型的事物。您的 AST 使用动态多态性。

      这消除了编写简洁生成器的任何机会,除非使用许多复杂的语义动作。我不记得写过很多与 Karma 相关的明确答案,但动态多态性和语义动作的问题在 Qi 方面都差不多:

      所有主要缺点都适用,除了显然没有创建 AST,因此分配的性能影响不如 Qi 解析器严重。

      但是,相同的逻辑仍然存在:Karma 生成器静态组合以提高效率。但是,您的动态类型层次结构排除了大部分效率。换句话说,你不是 Karma 的目标受众。

    • 无论您的 AST 是如何设计的,Karma 都有另一个结构性限制会在这里受到影响:(非常)难以利用有状态规则进行漂亮的打印。

      对我来说,这是几乎从不使用 Karma 的一个关键原因。即使漂亮的打印不是目标,您仍然可以获得类似的里程,只需直接使用 Boost Fusion 生成访问 AST 的输出(我们在我们的项目中使用它来生成 API 类型的不同版本的 OData XML 和 JSON 表示形式,以用于 restful API )。

      当然,有一些有状态生成任务具有内置到 Karma 的自定义指令,有时它们会达到快速原型设计的最佳位置,例如

    不管怎样,让我们​​做吧

    因为我不是受虐狂,所以我会从other answer 借用一个概念:创建一个中间表示,以更好地促进 Karma。

    在此示例中,中间表示可能非常简单,但我怀疑您的其他要求(例如 "for example, asciimath or other formats created by us")将需要更详细的设计。

    ///////////////////////////////////////////////////////////////////////////////
    // A simple intermediate representation
    #include <boost/variant.hpp>
    namespace output_ast {
        struct Function;
        struct Value;
        using Expression = boost::variant<Function, Value>;
    
        using Arguments = std::vector<Expression>;
    
        struct Value    { std::string name, value; };
        struct Function { std::string name; Arguments args; };
    }
    

    首先,因为我们要使用 Karma,所以我们确实需要实际调整中间表示:

    #include <boost/fusion/include/struct.hpp>
    BOOST_FUSION_ADAPT_STRUCT(output_ast::Value, name, value)
    BOOST_FUSION_ADAPT_STRUCT(output_ast::Function, name, args)
    

    生成器

    这是我能想到的最简单的生成器,给予和接受两件事:

    • 我已经对其进行了相当长的调整,以获得一些“可读”的格式。如果您删除所有无关紧要的空格,它会变得更简单。
    • 我选择不存储冗余信息(例如中间表示中的静态“类型”表示)。这样做会稍微简单一些,主要是通过使type 规则更类似于namevalue
    namespace karma_json {
        namespace ka = boost::spirit::karma;
    
        template <typename It>
        struct Generator : ka::grammar<It, output_ast::Expression()> {
            Generator() : Generator::base_type(expression) {
                expression = function|value;
    
                function
                    = "{\n  " << ka::delimit(",\n  ") 
                       [name << type(+"Function") ]
                    << arguments 
                    << "\n}"
                    ;
    
                arguments = "\"arguments\": [" << -(("\n  " << expression) % ",") << ']';
    
                value
                    = "{\n  " << ka::delimit(",\n  ") 
                        [name << type(+"Value") ]
                    << value_ 
                    << "\n}"
                    ;
    
                type   = "\"type\":\"" << ka::string(ka::_r1) << "\"";
                string = '"' << *('\\' << ka::char_("\\\"") | ka::char_) << '"';
                name   = "\"name\":" << string;
                value_ = "\"value\":" << string;
            }
    
          private:
            ka::rule<It, output_ast::Expression()> expression;
            ka::rule<It, output_ast::Function()> function;
            ka::rule<It, output_ast::Arguments()> arguments;
            ka::rule<It, output_ast::Value()> value;
            ka::rule<It, std::string()> string, name, value_;
            ka::rule<It, void(std::string)> type;
        };
    }
    

    后记

    为了完整起见,我进行了简化。并遇到了这个 excellent 演示完全不明显的属性处理怪癖。以下(只是剥离空白处理)工作:

    function = '{' << ka::delimit(',') [name << type] << arguments << '}';
    value = '{' << ka::delimit(',') [name << type] << value_ << '}' ;
    

    如果您喜欢戏剧,可以阅读错误小说here。问题是delimit[] 块神奇地将属性合并到一个字符串中(呵呵)。错误消息反映了字符串属性没有被使用,例如启动arguments 生成器。

    治疗症状最直接的办法就是拆属性,但没有真正的办法:

    function = '{' << ka::delimit(',') [name << ka::eps << type] << arguments << '}';
    value = '{' << ka::delimit(',') [name << ka::eps << type] << value_ << '}' ;
    

    没有区别

    function = '{' << ka::delimit(',') [ka::as_string[name] << ka::as_string[type]] << arguments << '}';
    value = '{' << ka::delimit(',') [ka::as_string[name] << ka::as_string[type]] << value_ << '}' ;
    

    如果它真的有效,那就太好了。没有任何添加包含或替换为ka::as&lt;std::string&gt;()[...] 之类的咒语使编译错误消失。²

    所以,为了结束这个悲伤的故事,我们将陷入令人麻木的乏味:

    function = '{' << name << ',' << type << ',' << arguments << '}';
    arguments = "\"arguments\":[" << -(expression % ',') << ']';
    

    有关现场演示,请参阅下面标有“简化版”的部分。

    使用它

    使用该语法生成​​的最短方法是创建中间表示:

    ///////////////////////////////////////////////////////////////////////////////
    // Expression -> output_ast
    struct serialization {
        static output_ast::Expression call(Expression const* e) {
            if (auto* f = dynamic_cast<Function const*>(e)) {
                output_ast::Arguments args;
                for (auto& a : f->m_arguments) args.push_back(call(a));
                return output_ast::Function { f->getName(), args };
            }
    
            if (auto* v = dynamic_cast<Value const*>(e)) {
                return output_ast::Value { v->getName(), v->getValue() };
            }
    
            return {};
        }
    };
    
    auto to_output(Expression const* expression) {
        return serialization::call(expression);
    }
    

    然后使用它:

    using It = boost::spirit::ostream_iterator;
    std::cout << format(karma_json::Generator<It>{}, to_output(plus1));
    

    完整演示

    Live On Wandbox¹

    #include <boost/lexical_cast.hpp>
    #include <iostream>
    #include <vector>
    
    struct Expression {
        virtual std::string getName() const = 0;
    };
    
    struct Value : Expression {
        virtual std::string getValue() const = 0;
    };
    
    struct IntegerValue : Value {
        IntegerValue(int value) : m_value(value) {}
        virtual std::string getName() const override { return "IntegerValue"; }
        virtual std::string getValue() const override { return boost::lexical_cast<std::string>(m_value); }
    
      private:
        int m_value;
    };
    
    struct Function : Expression {
        void addArgument(Expression *expression) { m_arguments.push_back(expression); }
        virtual std::string getName() const override { return m_name; }
    
      protected:
        std::vector<Expression *> m_arguments;
        std::string m_name;
    
        friend struct serialization;
    };
    
    struct Plus : Function {
        Plus() : Function() { m_name = "Plus"; }
    };
    
    ///////////////////////////////////////////////////////////////////////////////
    // A simple intermediate representation
    #include <boost/variant.hpp>
    namespace output_ast {
        struct Function;
        struct Value;
        using Expression = boost::variant<Function, Value>;
    
        using Arguments = std::vector<Expression>;
    
        struct Value    { std::string name, value; };
        struct Function { std::string name; Arguments args; };
    }
    
    #include <boost/fusion/include/struct.hpp>
    BOOST_FUSION_ADAPT_STRUCT(output_ast::Value, name, value)
    BOOST_FUSION_ADAPT_STRUCT(output_ast::Function, name, args)
    
    #include <boost/spirit/include/karma.hpp>
    namespace karma_json {
        namespace ka = boost::spirit::karma;
    
        template <typename It>
        struct Generator : ka::grammar<It, output_ast::Expression()> {
            Generator() : Generator::base_type(expression) {
                expression = function|value;
    
                function
                    = "{\n  " << ka::delimit(",\n  ") 
                       [name << type(+"Function") ]
                    << arguments 
                    << "\n}"
                    ;
    
                arguments = "\"arguments\": [" << -(("\n  " << expression) % ",") << ']';
    
                value
                    = "{\n  " << ka::delimit(",\n  ") 
                        [name << type(+"Value") ]
                    << value_ 
                    << "\n}"
                    ;
    
                type   = "\"type\":\"" << ka::string(ka::_r1) << "\"";
                string = '"' << *('\\' << ka::char_("\\\"") | ka::char_) << '"';
                name   = "\"name\":" << string;
                value_ = "\"value\":" << string;
            }
    
          private:
            ka::rule<It, output_ast::Expression()> expression;
            ka::rule<It, output_ast::Function()> function;
            ka::rule<It, output_ast::Arguments()> arguments;
            ka::rule<It, output_ast::Value()> value;
            ka::rule<It, std::string()> string, name, value_;
            ka::rule<It, void(std::string)> type;
        };
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // Expression -> output_ast
    struct serialization {
        static output_ast::Expression call(Expression const* e) {
            if (auto* f = dynamic_cast<Function const*>(e)) {
                output_ast::Arguments args;
                for (auto& a : f->m_arguments) args.push_back(call(a));
                return output_ast::Function { f->getName(), args };
            }
    
            if (auto* v = dynamic_cast<Value const*>(e)) {
                return output_ast::Value { v->getName(), v->getValue() };
            }
    
            return {};
        }
    };
    
    auto to_output(Expression const* expression) {
        return serialization::call(expression);
    }
    
    int main() {
        // Build expression 4 + 5 + 6 as 4 + (5 + 6)
        Function *plus1 = new Plus();
        Function *plus2 = new Plus();
        Value *iv4 = new IntegerValue(4);
        Value *iv5 = new IntegerValue(5);
        Value *iv6 = new IntegerValue(6);
        plus2->addArgument(iv5);
        plus2->addArgument(iv6);
        plus1->addArgument(iv4);
        plus1->addArgument(plus2);
    
        // Generate json string here, but how?
        using It = boost::spirit::ostream_iterator;
        std::cout << format(karma_json::Generator<It>{}, to_output(plus1));
    }
    

    输出

    生成器的可读性/健壮性/功能性如我所愿(存在与分隔符相关的怪癖,当类型包含需要引用的字符时存在问题,没有状态缩进)。

    结果看起来不像预期的那样,虽然它是有效的 JSON:

    {
      "name":"Plus",
      "type":"Function",
      "arguments": [
      {
      "name":"IntegerValue",
      "type":"Value",
      "value":"4"
    },
      {
      "name":"Plus",
      "type":"Function",
      "arguments": [
      {
      "name":"IntegerValue",
      "type":"Value",
      "value":"5"
    },
      {
      "name":"IntegerValue",
      "type":"Value",
      "value":"6"
    }]
    }]
    }
    

    修复它是一个不错的挑战,如果你想尝试的话。

    简化版

    简化版本,包含上面记录的属性处理解决方法:

    Live On Coliru

    namespace karma_json {
        namespace ka = boost::spirit::karma;
    
        template <typename It>
        struct Generator : ka::grammar<It, output_ast::Expression()> {
            Generator() : Generator::base_type(expression) {
                expression = function|value;
    
                function = '{' << name << ',' << type << ',' << arguments << '}';
                arguments = "\"arguments\":[" << -(expression % ',') << ']';
    
                value = '{' << name << ',' << type << ',' << value_ << '}' ;
    
                string = '"' << *('\\' << ka::char_("\\\"") | ka::char_) << '"';
                type   = "\"type\":" << string;
                name   = "\"name\":" << string;
                value_ = "\"value\":" << string;
            }
    
          private:
            ka::rule<It, output_ast::Expression()> expression;
            ka::rule<It, output_ast::Function()> function;
            ka::rule<It, output_ast::Arguments()> arguments;
            ka::rule<It, output_ast::Value()> value;
            ka::rule<It, std::string()> string, name, type, value_;
        };
    }
    

    产生以下输出:

    {"name":"Plus","type":"Function","arguments":[{"name":"IntegerValue","type":"Value","value":"4"},{"name":"Plus","type":"Function","arguments":[{"name":"IntegerValue","type":"Value","value":"5"},{"name":"IntegerValue","type":"Value","value":"6"}]}]}
    

    我倾向于认为这是比“漂亮”格式化失败的尝试更好的成本/收益比很多。但这里的真实情况是,维护成本无论如何都是天价。


    ¹有趣的是,Coliru 超过了编译时间...这也可能是指导您的设计决策的一个论点

    ² 让您想知道有多少人每天实际使用 Karma

    【讨论】:

    • 我在犹豫我浪费了多少时间与 Karma 战斗来完成 ADT/多态版本。这是最终奏效的东西:wandbox.org/permlink/H4ybpbM5hEi2c78x TL/DR:你不能对 Karma 使用多态类型,当然不能使用抽象类型。所有属性都必须是可复制和可默认构造的。 BOOST_ADAPT_ADT_NAMED 不适用于不可变属性(喘气,为什么)等。无论如何,现在您拥有所有信息来做出决策。
    猜你喜欢
    • 1970-01-01
    • 2018-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多