【问题标题】:C++ Mutually Recursive Variant Type (Again)C++ 相互递归变体类型(再次)
【发布时间】:2017-08-21 18:42:14
【问题描述】:

我遇到了与此处所述类似的问题:C++ Mutually Recursive Variant Type

我正在尝试在 C++ 中创建 JSON 表示。许多库已经提供了 优秀 JSON 表示和非常快的解析器,但我并没有重新发明这个轮子。我需要创建一个在特定条件下支持某些空间优化的 C++ JSON 表示。简而言之,当且仅当 JSON 数组包含同质数据,而不是将每个元素存储为臃肿的变体类型时,我才需要压缩存储原生类型。我还需要支持异构数组和标准的嵌套 JSON 对象。

以下是代码的“如果愿望是马,乞丐会骑”版本的代码,旨在清楚地说明意图,但由于在任何声明存在之前就使用了类型,因此显然被破坏了。我想避免在类型中多次指定相同的信息(即数组、对象和值不应该需要重复的类型规范)。我还想避免任何不必要的高运行时成本。

#include <string>
#include <unordered_map>
#include <vector>
#include <boost/variant.hpp>
#include <boost/variant/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>

class JSONDocument {
    public:
        using String = std::string;
        using Integer = long;
        using Float = double;
        using Boolean = bool;
        using Null = void *;
        using Key = std::string;
        using Path = std::string;
        using Value = boost::variant<
                Null,
                String,
                Integer,
                Float,
                Boolean,
                Object,
                Array
                >;
        using Object = std::unordered_map<Key,Value>;
        using Array = boost::variant<
                std::vector<Null>,
                std::vector<String>,
                std::vector<Integer>,
                std::vector<Float>,
                std::vector<Boolean>,
                std::vector<Value> >;
    private:
        Value root;
        class value_traversal_visitor : public boost::static_visitor<Value> {
            public:
                value_traversal_visitor( Path path ) : path(path) {}
                Value operator()( Null x ) const {
                    if( path.empty() ) {
                        return x;
                    }
                    // otherwise throw ...
                }
                Value operator()( String x ) const {
                    if( path.empty() ) {
                        return x;
                    }
                }
                ...
                // special handling for Array and Object types
            private:
                Path path;
        };
    public:
        Value get( Path path ) {
            return boost::apply_visitor( value_traversal_visitor( path ), root );
        }
        ...
};

如您所见,我包含了recursive_wrapper 标头。我尝试过各种 boost::make_recursive_variant 和 boost::recursive_wrapper 的调用,但我总是遇到编译器错误。我看不到C++ Mutually Recursive Variant Type 的答案如何解决这个问题,因为在每次尝试中,我都会遇到编译器错误(来自 gcc++ 5.3 和 LLVM/clang++ 3.8),这些错误几乎完全引用了 Boost,本质上归结为不可转换的类型或声明冲突或不存在。我会将我的一项尝试与特定的编译器错误消息一起放在这里,但我不知道要使用哪些尝试。

我希望有人可以让我走上正确的道路......

提前致谢!

编辑

只是在下面接受的答案的基础上,这里是类型及其用法的工作框架示例。

#include <string>
#include <unordered_map>
#include <vector>
#include <boost/variant.hpp>
#include <boost/variant/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>

using String = std::string;
using Integer = long;
using Float = double;
using Boolean = bool;
using Key = std::string;

using Value = boost::make_recursive_variant<
        String,
        Integer,
        Float,
        Boolean,
        std::unordered_map<Key, boost::recursive_variant_>,
        boost::variant<std::vector<String>,std::vector<Integer>,std::vector<Float>,std::vector<Boolean>,std::vector<boost::recursive_variant_> >
        >::type;

using Object = std::unordered_map<Key, Value>;

using Array = boost::variant<std::vector<String>,std::vector<Integer>,std::vector<Float>,std::vector<Boolean>,std::vector<Value> >;

int main( int argc, char* argv[] ) {
    Value v;
    v = static_cast<Integer>( 7 );
    Object o;
    v = o;
    Array a = std::vector<Integer>( 3 );
    v = a;
    return 0;
}

【问题讨论】:

  • 为什么不将 unique_ptr 用于递归类型?这基本上就是包装器所做的。
  • @MichaëlRoy 我对整个项目最大的问题之一就是如何正确地声明。如果我根据 unique_ptr 和 unique_ptr 定义值,则必须前向声明。但是前向声明必须是完全相同的 typedef 或类,对吗?如果我使用类转发声明,那么当我继续最终为转发声明的名称定义 typedef 时,会出现类型不匹配错误。另一方面,使用 typedef 进行前向声明要求递归类型的声明已经以其正确的最终形式存在。
  • 如果一切都失败了,您可以将有问题的类型包装在一个结构中。这你可以前向声明。我不久前写了一个 AST,特别是我的一些向量必须这样做。你最终得到一个 unique_ptr。我知道这很丑,但它确实有效。
  • @MichaëlRoy 这就是他们在下面提供的链接中采用的最终方法。我认为这是一种完全灵活的方法,如果做得好,不一定会涉及任何额外的运行时间成本,但我无法摆脱这样一种感觉,即应该有一种方法可以仅使用变体设施来做到这一点。为了其他有类似问题的人,我会本着这种精神寻找答案。也许 sehe 还可以提供一个。

标签: c++ json boost boost-variant


【解决方案1】:

你可以use recursive_variant_ placeholder with make_recursive_variant

这里是要点:

using Value   = boost::make_recursive_variant<
    Null, 
    String, 
    Integer, 
    Float, 
    Boolean,
    std::unordered_map<Key, boost::recursive_variant_>, // Object
    std::vector<boost::recursive_variant_>              // Array
>::type;
using Object = std::unordered_map<Key, Value>;
using Array = boost::variant<Value>;

现场演示

Live On Coliru

如您所见,代码中有未实现的位(永远不要编写缺少返回语句的函数!)。还要注意 get 和私有访问者实现的控制流的简化。

#include <boost/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/variant/variant.hpp>
#include <string>
#include <unordered_map>
#include <vector>

class JSONDocument {
  public:
    struct Null { constexpr bool operator==(Null) const { return true; } };
    using String  = std::string;
    using Integer = long;
    using Float   = double;
    using Boolean = bool;
    using Key     = std::string;
    using Path    = std::string;
    using Value   = boost::make_recursive_variant<
        Null, 
        String, 
        Integer, 
        Float, 
        Boolean,
        std::unordered_map<Key, boost::recursive_variant_>, // Object
        std::vector<boost::recursive_variant_>              // Array
    >::type;
    using Object = std::unordered_map<Key, Value>;
    using Array = boost::variant<Value>;

  private:
    Value root;

    struct value_traversal_visitor {
        Path path;
        using result_type = Value;

        result_type operator()(Value const &x) const {
            if (path.empty()) {
                return x;
            }
            return boost::apply_visitor(*this, x);
        }

        result_type operator()(Null)           const { throw std::invalid_argument("null not addressable"); }
        result_type operator()(String const &) const { throw std::invalid_argument("string not addressable"); }

        // special handling for Array and Object types TODO
        template <typename T> result_type operator()(T &&) const { return Null{}; }
    };

  public:
    Value get(Path path) { return value_traversal_visitor{path}(root); }
};

int main() {}

注意事项

  • 请注意,您不应将 void* 用于 Null,因为会出现各种不需要的隐式转换
  • 请注意,您可能不应该使用unordered_map,因为

    • 一些 JSON 实现允许重复的属性名称
    • 一些 JSON 应用程序依赖于属性的顺序

另见https://github.com/sehe/spirit-v2-json/blob/master/json.hpp#L37

【讨论】:

  • 这确实很有帮助。我从 void* 更改了 Null 表示,并且链接代码在多大程度上与我在意图和细节上的目标相似是有用的。然而,答案完全回避了一个核心的复杂性,这是我必须实现的特定优化。我的 Array 类型不仅仅是 Value typedef 的封装,因为这会导致每个元素都是一个变体,这是我无法承受的。 Array 本身必须是一个变体类型的向量,其中 Value 是唯一的一个,而 Value 类型引用这个变体类型的数组。
  • 关于我之前的评论,这似乎从根本上使我的要求与 Boost recursive_variant 工具的典型用途不同。 Value 变体必须具有另一个变体类型,即 Array 变体作为类型成员,然后它必须具有包含 Value 变体的类型成员。同时,还没有定义 Array 变体和 Value 变体,两者都需要另一个。这是导致我每次尝试都因编译器喷射而失败的复杂因素。这似乎不是一个变体递归引用自身的简单案例。
  • 好吧,这确实概括了我所需要的。对于所有对确切方式感兴趣的人,请查看上面的编辑。 Array 在两个地方的定义完全相同自然很重要,这是我不喜欢的脆弱性,但似乎没有办法绕过它。
【解决方案2】:

本身不是解决方案,但这是一种使用 std::variant 实现变体递归的方法。我认为这可能很有趣,因为 stl 没有为递归或前向声明的类型提供任何 api。使用gcc 7.2 -std=c++17编译

#include <variant>
#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

struct Nil {};

struct vector1;

using var_t1 = variant<Nil, int, vector1>;
using var_t2 = variant<Nil, double, float, int, var_t1>;

struct vector1 { 
    vector<var_t2> v_; 
};

struct print_var_t2;

struct print_var_t1 {
    void operator()(const vector1& v);
    void operator()(int) { cout << "int\n"; }
    void operator()(const Nil&) { cout  << "nil\n"; }
};

struct print_var_t2 {
    void operator()(const Nil&) { cout << "Nil\n"; } 
    void operator()(int)  { cout << "int\n"; }
    void operator()(double) { cout << "double\n"; }
    void operator()(float)  { cout << "float\n"; }
    void operator()(const var_t1& v);
};

void print_var_t1::operator()(const vector1& v) {
    for_each(v.v_.begin(), v.v_.end(), [](const var_t2& x)
    {
        visit(print_var_t2{}, x);
    });
}

void print_var_t2::operator()(const var_t1& v) {
    visit(print_var_t1{}, v);    
}

int main()
{
    vector1 v1;
    v1.v_.push_back(.1);
    v1.v_.push_back(2.f);
    v1.v_.push_back(3);
    v1.v_.push_back(var_t2{3});

    var_t1 var1 = v1;

    std::visit(print_var_t1{}, var1);
    return 0;
}

【讨论】:

  • 这些变体不是递归的。你定义了一个recursive_variant,它看起来应该被命名为recursive_wrapper。但它没有被使用。如果你使用过它,它会遇到完全相同的问题,因为它需要模板参数 T 也被定义为一个完整的类型,AFAICT。
  • 1.感谢您的指正。 2. 但如果 T 是前向声明的结构,则不是。
  • 向前声明的是“不完整类型”的定义。你不能对它们做一些事情(比如,任何需要知道sizeof(T)的操作,比如,你知道,声明一个T类型的成员变量)
  • 没有人对此提出异议。
  • 删除坏位后,您的答案表明您可以使用std::variant&lt;&gt; 而不是boost::variant&lt;&gt; 编写相同的内容。这是因为std::vector&lt;&gt; 实际上是一个多值 recursive_wrapper。
猜你喜欢
  • 1970-01-01
  • 2015-06-10
  • 2013-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-25
  • 1970-01-01
相关资源
最近更新 更多