Взаимно рекурсивный вариантный тип C++ (снова)

У меня есть проблема, аналогичная описанной здесь: C++ Взаимно рекурсивный вариантный тип

Я пытаюсь создать представление JSON в C++. Многие библиотеки уже предлагают отличные JSON-представления и парсеры, которые очень быстрые, но я не изобретаю это колесо заново. Мне нужно создать представление CSON JSON, которое поддерживает определенную оптимизацию пространства при определенных условиях. Короче говоря, если и только если массив JSON содержит однородные данные, а не хранить каждый элемент в виде раздутых типов вариантов, мне нужно компактное хранение нативных типов. Мне также нужно поддерживать гетерогенные массивы и стандартные вложенные объекты JSON.

Ниже приведена версия кода "если бы желания были лошадьми, нищие бы покатались", которая предназначена для того, чтобы ясно проиллюстрировать намерение, но, очевидно, не подходит, потому что типы используются до того, как существует какое-либо объявление. Я хочу не указывать одну и ту же информацию несколько раз в типах (т. Е. Array, Object и Value не должны требовать дублированных спецификаций типов). Я также хочу избежать любых излишне высоких затрат времени выполнения.

#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;
}

2 ответа

Решение

Вы могли бы просто использовать recursive_variant_ заполнитель с 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 Demo

Жить на Колиру

Как вы можете видеть, в коде есть невыполненные биты (никогда не пишите функции, в которых отсутствуют операторы возврата!). Также обратите внимание на упрощение потока управления для 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

Не решение само по себе, но вот способ достижения рекурсивности вариантов с использованием std::variable. Я подумал, что это может быть интересно, так как 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;
}
Другие вопросы по тегам