rg6
rg6

Reputation: 329

std::variant, a wrapper class, and 'conversion from ... to non-scalar type ... requested'

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!

Object Class

#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

Array Class

#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

Value Class

#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

Type Definitions

#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

Main Code

#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

Answers (1)

rg6
rg6

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.

Object Class

#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

Array Class

#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

Value Class

#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

Type Definitions

#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

Main Code

#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

Related Questions