Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Mutually Recursive Variant Type (Again)

I have a problem similar to that described here: C++ Mutually Recursive Variant Type

I am trying to create a JSON representation in C++. Many libraries already offer excellent JSON representations and parsers that are very fast, but I am not reinventing this wheel. I need to create a C++ JSON representation that supports certain space optimizations under specific conditions. In short, if and only if a JSON array contains homogenous data, rather than storing every element as bloated variant types, I need compact storage of native types. I also need to support heterogeneous arrays and standard nested JSON objects.

The following is the "if wishes were horses, beggars would ride" version of the code, which is meant to clearly illustrate intent, but is obviously broken because types are used before any declaration exists. I want to avoid specifying the same information multiple times in types (i.e. Array, Object, and Value should not require duplicated type specifications). I also want to avoid any unnecessarily high run-time costs.

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

As you can see, I am including the recursive_wrapper header. I have tried various invocations of boost::make_recursive_variant and boost::recursive_wrapper, but I always get compiler errors. I do not see how the answer from C++ Mutually Recursive Variant Type solves this, because in every attempt, I get compiler errors (from both gcc++ 5.3 and LLVM/clang++ 3.8) that almost exclusively reference Boost that essentially boil down to types not being convertible or declarations either conflicting or not existing. I would put one of my attempts along with specific compiler error messages here, but I wouldn't know which of the many attempts to use.

I'm hoping somebody can set me on the right path...

Thanks in advance!

Edit

Just to build on the accepted answer below, here is an example of a working skeleton for the types and their usages.

#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;
}
like image 712
rg6 Avatar asked Aug 21 '17 18:08

rg6


2 Answers

You could just use recursive_variant_ placeholder with make_recursive_variant.

Here's the gist:

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

Live On Coliru

As you can see there's unimplemented bits in the code (never write functions missing return statements!). Also note the simplifications in control flow for get and the private visitor implementation.

#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() {}

CAVEATS

  • Note that you should NOT use void* for Null because all manner of unwanted implicit conversions
  • Note that you should probably not use unordered_map because

    • some JSON implementations allow duplicate property names
    • some JSON applications depend on the ordering of the properties

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

like image 73
sehe Avatar answered Oct 11 '22 11:10

sehe


Not a solution per se, but Here's a way to achieve variant recursivity using std::variant. I thought this might be of interest, since the stl doesn't provide any api for recursive, nor forward-declared types. Compiles using 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;
}
like image 37
Michaël Roy Avatar answered Oct 11 '22 11:10

Michaël Roy