Reputation: 329
I have mutually recursive variant types: Value, Array, and Object. The crux of the matter is that I can assign one of the variant types to a Value when it is nested within an Array or Object, which can both contain Value. I cannot assign directly to a Value. The main()
function code at the bottom should make this clear. The compiler (GNU GCC 7.2) gives me errors like:
error: conversion from 'JSON::Integer {aka long unsigned int}' to non-scalar type 'JSON::Value' requested
Value v = Integer( 7 );
and oddly does not provide any additional information about assignment operator candidates or anything else. Further, it isn't clear to me how assignment operators I have defined in Value are affecting this problem. Complete working example code follows. Any assignment of one of the variant types directly to a Value type in main()
causes a compiler error just like the one above for the Integer
type.
Thanks in advance!
#ifndef JSON_OBJECT_HPP
#define JSON_OBJECT_HPP
#include <iomanip>
#include <string>
#include <unordered_map>
#include "Value.hpp"
namespace JSON {
class Object {
public:
using Key = std::string;
private:
using values_t = std::unordered_map<Key,Value>;
values_t values;
public:
Object() = default;
Value & operator[]( Key const & key ) {
auto it = values.emplace( key, Value() );
return it.first->second;
}
Value const & operator[]( Key const & key ) const {
auto it = values.find( key );
return it->second;
}
bool has_key( Key const & key ) const {
auto it = values.find( key );
return it != values.end();
}
bool operator==( Object const & rhs ) const {
return values == rhs.values;
}
values_t::const_iterator begin() const {
return values.begin();
}
values_t::const_iterator end( ) const {
return values.end();
}
values_t::iterator begin() {
return values.begin();
}
values_t::iterator end() {
return values.end();
}
};
bool operator==( Object const & lhs, Object const & rhs ) {
return lhs.operator==( rhs );
}
std::ostream & operator<<( std::ostream & os, Object const & object ) {
os << '{';
auto begin = object.begin();
auto end = object.end();
if( begin != end ) {
os << std::quoted( begin->first ) << ':' << begin->second;
}
while( ++begin != end ) {
os << ',' << std::quoted( begin->first ) << ':' << begin->second;
}
os << '}';
}
}
#endif
#ifndef JSON_ARRAY_HPP
#define JSON_ARRAY_HPP
#include <vector>
#include "types.hpp"
#include "Value.hpp"
namespace JSON {
std::ostream & operator<<( std::ostream &, Value & );
class Array {
private:
using values_t = std::vector<Value>;
values_t values;
public:
using Key = values_t::size_type;
Array() = default;
Value & operator[]( Key key ) {
if( !has_key( key ) ) {
values.resize( key + 1 );
}
return values[key];
}
Value const & operator[]( Key key ) const {
return values[key];
}
bool has_key( Key key ) const {
return key < values.size();
}
bool operator==( Array const & rhs ) const {
return values == rhs.values;
}
values_t::const_iterator begin() const {
return values.begin();
}
values_t::const_iterator end( ) const {
return values.end();
}
values_t::iterator begin() {
return values.begin();
}
values_t::iterator end() {
return values.end();
}
};
bool operator==( Array const & lhs, Array const & rhs ) {
return lhs.operator==( rhs );
}
std::ostream & operator<<( std::ostream & os, Array const & array ) {
os << '[';
auto begin = array.begin();
auto end = array.end();
if( begin != end ) {
os << *begin;
}
while( ++begin != end ) {
os << ',' << *begin;
}
os << ']';
}
}
#endif
#ifndef JSON_VALUE_HPP
#define JSON_VALUE_HPP
#include <iomanip>
#include <type_traits>
#include <variant>
#include <boost/variant/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include "types.hpp"
namespace JSON {
class Object;
class Array;
bool operator==( Object const & lhs, Object const & rhs );
bool operator==( Array const & lhs, Array const & rhs );
std::ostream & operator<<( std::ostream &, Object const & object );
std::ostream & operator<<( std::ostream &, Array const & array );
template<class T> struct always_false : std::false_type {};
class Value {
private:
using variant_t = std::variant<Undefined,String,Integer,Number,boost::recursive_wrapper<Object>,boost::recursive_wrapper<Array> >;
variant_t data;
friend std::ostream & operator<<( std::ostream & os, Value const & value );
public:
Value() = default;
bool operator==( Value const & rhs ) const {
return std::visit(
[]( auto && lhs, auto && rhs ) -> bool {
using lhsT = std::decay_t<decltype( lhs )>;
using rhsT = std::decay_t<decltype( rhs )>;
if constexpr ( std::is_same_v< lhsT, rhsT> ) {
if constexpr (std::is_same_v< lhsT, boost::recursive_wrapper<Object> > ) {
return lhs.get() == rhs.get();
} else if constexpr (std::is_same_v< lhsT, boost::recursive_wrapper<Array> > ) {
return lhs.get() == rhs.get();
} else {
return lhs == rhs;
}
} else {
return false;
}
},
data,
rhs.data
);
}
Value & operator=( String const & rhs ) {
data = rhs;
return *this;
}
Value & operator=( Integer const & rhs ) {
data = rhs;
return *this;
}
Value & operator=( Object const & rhs ) {
data = rhs;
return *this;
}
Value & operator=( Array const & rhs ) {
data = rhs;
return *this;
}
};
std::ostream & operator<<( std::ostream & os, Value const & value ) {
std::visit(
[&os]( auto && arg ) {
using T = std::decay_t<decltype( arg )>;
if constexpr ( std::is_same_v< T, Undefined > ) {
os << "undefined";
} else if constexpr ( std::is_same_v< T, String > ) {
os << std::quoted( arg );
} else if constexpr ( std::is_same_v< T, Integer > ) {
os << arg;
} else if constexpr ( std::is_same_v< T, Number > ) {
os << arg;
} else if constexpr ( std::is_same_v< T, boost::recursive_wrapper<Object> > ) {
os << arg.get();
} else if constexpr ( std::is_same_v< T, boost::recursive_wrapper<Array> > ) {
os << arg.get();
} else if constexpr ( std::is_same_v< T, Boolean > ) {
os << (arg == false ? "false" : "true");
} else if constexpr ( std::is_same_v< T, Null > ) {
os << "null";
} else {
static_assert( always_false<T>::value, "non-exhaustive visitor" );
}
},
value.data
);
}
}
#endif
#ifndef TYPES_HPP
#define TYPES_HPP
namespace JSON {
template <typename Tag> struct Literal {
bool operator==( Literal const & ) const {
return true;
}
bool operator<( Literal const & ) const {
return false;
}
};
using String = std::string;
using Integer = uint64_t;
using Number = double;
class Object;
class Array;
using Boolean = bool;
using Null = Literal<struct tag_null>;
using Undefined = Literal<struct tag_undefined>;
}
#endif
#include <iostream>
#include "Array.hpp"
#include "Object.hpp"
#include "Value.hpp"
using namespace JSON;
int main() {
Object o;
o["fun"] = "what?"; // compiles fin
o["stuff"] = "yeah!"; // compiles fine
o["inttest"] = Integer( 44 ); // compiles fine
Array a;
a[2] = "yo"; // compiles fine
a[3] = Integer( 6 ); // compiles fine
o["arrtest"] = a;
// Value v = a; // fails to compile
Value v = Integer( 7 ); // fails to compile
std::cout << v;
std::cout << o << "\n";
std::cout << a << "\n";
return 0;
}
Upvotes: 2
Views: 949
Reputation: 329
This was a bad question with an embarrassing oversight. As the commenter pointed out, the problematic lines in main()
were initializations rather than assignments. Providing the appropriate constructors solved the problem. For completeness, corrected code follows. It compiles and works fine under GNU GCC 7.2.
#ifndef JSON_OBJECT_HPP
#define JSON_OBJECT_HPP
#include <iomanip>
#include <string>
#include <unordered_map>
#include "Value.hpp"
namespace JSON {
class Object {
public:
using Key = std::string;
private:
using values_t = std::unordered_map<Key,Value>;
values_t values;
public:
Object() = default;
Value & operator[]( Key const & key ) {
auto it = values.emplace( key, Value() );
return it.first->second;
}
Value const & operator[]( Key const & key ) const {
auto it = values.find( key );
return it->second;
}
bool has_key( Key const & key ) const {
auto it = values.find( key );
return it != values.end();
}
bool operator==( Object const & rhs ) const {
return values == rhs.values;
}
values_t::const_iterator begin() const {
return values.begin();
}
values_t::const_iterator end( ) const {
return values.end();
}
values_t::iterator begin() {
return values.begin();
}
values_t::iterator end() {
return values.end();
}
};
bool operator==( Object const & lhs, Object const & rhs ) {
return lhs.operator==( rhs );
}
std::ostream & operator<<( std::ostream & os, Object const & object ) {
os << '{';
auto begin = object.begin();
auto end = object.end();
if( begin != end ) {
os << std::quoted( begin->first ) << ':' << begin->second;
}
while( ++begin != end ) {
os << ',' << std::quoted( begin->first ) << ':' << begin->second;
}
os << '}';
}
}
#endif
#ifndef JSON_ARRAY_HPP
#define JSON_ARRAY_HPP
#include <vector>
#include "types.hpp"
#include "Value.hpp"
namespace JSON {
std::ostream & operator<<( std::ostream &, Value const & );
class Array {
private:
using values_t = std::vector<Value>;
values_t values;
public:
using Key = values_t::size_type;
Array() = default;
Value & operator[]( Key key ) {
if( !has_key( key ) ) {
values.resize( key + 1 );
}
return values[key];
}
Value const & operator[]( Key key ) const {
return values[key];
}
bool has_key( Key key ) const {
return key < values.size();
}
bool operator==( Array const & rhs ) const {
return values == rhs.values;
}
values_t::const_iterator begin() const {
return values.begin();
}
values_t::const_iterator end( ) const {
return values.end();
}
values_t::iterator begin() {
return values.begin();
}
values_t::iterator end() {
return values.end();
}
};
bool operator==( Array const & lhs, Array const & rhs ) {
return lhs.operator==( rhs );
}
std::ostream & operator<<( std::ostream & os, Array const & array ) {
os << '[';
auto begin = array.begin();
auto end = array.end();
if( begin != end ) {
os << *begin;
}
while( ++begin != end ) {
os << ',' << *begin;
}
os << ']';
}
}
#endif
#ifndef JSON_VALUE_HPP
#define JSON_VALUE_HPP
#include <iomanip>
#include <type_traits>
#include <variant>
#include <boost/variant/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include "types.hpp"
namespace JSON {
class Object;
class Array;
bool operator==( Object const & lhs, Object const & rhs );
bool operator==( Array const & lhs, Array const & rhs );
std::ostream & operator<<( std::ostream &, Object const & object );
std::ostream & operator<<( std::ostream &, Array const & array );
template<class T> struct always_false : std::false_type {};
class Value {
private:
using variant_t = std::variant<Undefined,String,Integer,Number,boost::recursive_wrapper<Object>,boost::recursive_wrapper<Array> >;
variant_t data;
friend std::ostream & operator<<( std::ostream & os, Value const & value );
public:
Value() = default;
template <typename T> Value( T const & rhs ) : data( rhs ) {}
bool operator==( Value const & rhs ) const {
return std::visit(
[]( auto && lhs, auto && rhs ) -> bool {
using lhsT = std::decay_t<decltype( lhs )>;
using rhsT = std::decay_t<decltype( rhs )>;
if constexpr ( std::is_same_v< lhsT, rhsT> ) {
if constexpr (std::is_same_v< lhsT, boost::recursive_wrapper<Object> > ) {
return lhs.get() == rhs.get();
} else if constexpr (std::is_same_v< lhsT, boost::recursive_wrapper<Array> > ) {
return lhs.get() == rhs.get();
} else {
return lhs == rhs;
}
} else {
return false;
}
},
data,
rhs.data
);
}
};
std::ostream & operator<<( std::ostream & os, Value const & value ) {
std::visit(
[&os]( auto && arg ) {
using T = std::decay_t<decltype( arg )>;
if constexpr ( std::is_same_v< T, Undefined > ) {
os << "undefined";
} else if constexpr ( std::is_same_v< T, String > ) {
os << std::quoted( arg );
} else if constexpr ( std::is_same_v< T, Integer > ) {
os << arg;
} else if constexpr ( std::is_same_v< T, Number > ) {
os << arg;
} else if constexpr ( std::is_same_v< T, boost::recursive_wrapper<Object> > ) {
os << arg.get();
} else if constexpr ( std::is_same_v< T, boost::recursive_wrapper<Array> > ) {
os << arg.get();
} else if constexpr ( std::is_same_v< T, Boolean > ) {
os << (arg == false ? "false" : "true");
} else if constexpr ( std::is_same_v< T, Null > ) {
os << "null";
} else {
static_assert( always_false<T>::value, "non-exhaustive visitor" );
}
},
value.data
);
}
}
#endif
#ifndef JSON_TYPES_HPP
#define JSON_TYPES_HPP
namespace JSON {
template <typename Tag> struct Literal {
bool operator==( Literal const & ) const {
return true;
}
bool operator<( Literal const & ) const {
return false;
}
};
using String = std::string;
using Integer = uint64_t;
using Number = double;
class Object;
class Array;
using Boolean = bool;
using Null = Literal<struct tag_null>;
using Undefined = Literal<struct tag_undefined>;
}
#endif
#include <iostream>
#include "Array.hpp"
#include "Object.hpp"
#include "Value.hpp"
using namespace JSON;
int main() {
Object o;
o["fun"] = "what?";
o["stuff"] = "yeah!";
o["inttest"] = Integer( 44 );
Array a;
a[2] = "yo";
a[3] = Integer( 6 );
o["arrtest"] = a;
// Value v = a;
Value v = Integer( 7 );
std::cout << v << "\n";
std::cout << o << "\n";
std::cout << a << "\n";
return 0;
}
Upvotes: 1