I have an abstract class say A
which is implemented by a template class say B
, B
is specialized for a vector type that implements a copy constructor and a copy assignment operator, but when I compile i get the error: static assertion failed: result type must be constructible from value type of input range
. Declaring a virtual copy assignment in the abstract class does not help, as the signature there is: A & operator=(const A &);
but in B it is implemented for type B
so the signatures do not match.
Why I have this weird hierarchy? Because I am trying to implement a json parsing library, I need to have a container that can store string, number, bigint, boolean and array types, so I implemented the hierarchy to achieve type erasure, the template type is for these 5 types and need to specialize only for string and vector types.
Actual hierarchy (minimal reproducible example):
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <iostream>
class BasicJsonType {
public:
virtual std::string toString() const = 0;
virtual void setNull() = 0;
// copy assignment method
// virtual BasicJsonType& operator= (const BasicJsonType &value) = 0;
~BasicJsonType() = default;
};
template<typename T>
class BasicJsonTypeInterface: public BasicJsonType {
protected:
bool empty = true;
public:
virtual const T& get() = 0;
virtual void set(const T&) = 0;
};
namespace json {
// json::array is defined as
using array = std::vector<BasicJsonType>;
}
template <typename T>
class JsonValue {
T x;
public:
virtual std::string toString() {
return "";
}
virtual const T & get() {
return this->x;
}
virtual void set(const T &value) {
this->x = value;
}
};
template<>
class JsonValue<json::array>: public BasicJsonTypeInterface<json::array> {
std::shared_ptr<json::array> array;
public:
JsonValue() = delete;
JsonValue(const JsonValue<json::array> &value): JsonValue(*(value.array)) {
std::cout << "const JsonValue<json::array> &";
}
JsonValue(const json::array &array) {
std::cout << "const json::array &";
// error
this->array.reset(new json::array(array));
}
JsonValue(JsonValue<json::array> &&value): JsonValue(static_cast<json::array &&> (*(value.array)))
{ std::cout << "const JsonValue<json::array> &"; }
JsonValue(json::array &&array) {
this->array.reset(new json::array(std::move(array)));
this->empty = false;
}
virtual void setNull() { }
virtual const json::array &get() {
return *(this->array);
}
virtual void set(const json::array &value) {
this->array.reset(new json::array(value));
}
};
int main() {}
I created the interface type since I wanted to implement the get
, set
methods for all the types and irrespective of the type.
I searched for the error, and what I found is I am missing a copy function for the BasicJsonType
, like what it suggests here.
There maybe some design flaws in this, since it is my first try with anything of practical use with c++, I am targeting for c++11
.
std::vector<BasicJsonType>;
This is a useless type.
BasicJsonType
is an abstract class. Abstract classes are not value types. std::vector
stores value types.
std::vector
expects regular types (or semiregular if you don't need copy). Abstract types are NOT regular types.
There are a number of ways to have polymorphic regular types, but they all take work or 3rd party libraries.
A minor issue:
~BasicJsonType() = default;
this should be virtual.
...
There are a number of ways to approach your problem of getting a regular type to store in std::vector
.
Store unique_ptr<BasicJsonType>
in your vector
. This permits moving but not assignment*.
Implement a value_ptr
(based off unique ptr) that understands how to (virtually) clone its contents when copied.
Implement a cow_ptr
that uses a shared ptr under the hood for immutable data, and does a copy-on-write.
Create an any_with_interface
based off std::any
that guarantees the stored value matches an interface, and provides operator->
and *
that returns that interface.
Store a std::variant
of the various kinds of json concrete types. Write a helper function to get the abstract interface (if you need it).
As your set of supported types is closed (there are only so many json types), #5 is probably easiest.
class BasicJsonType {
public:
virtual std::string toString() const = 0;
virtual void setNull() = 0;
virtual bool isNull() const = 0;
protected: // no deleting through this interface
~BasicJsonType() = default;
};
// if we find this overload, remember to implement
// your own to_json_string for the type in question
template<class T>
std::string to_json_string( T const& ) = delete;
std::string to_json_string( std::string const& s ) { return s; }
std::string to_json_string( double const& d )
{
std::stringstream ss;
ss << d;
return ss.str();
}
template <typename T>
class JsonValue:public BasicJsonType {
public:
JsonValue() = default;
JsonValue(JsonValue const&) = default;
JsonValue(JsonValue &&) = default;
JsonValue& operator=(JsonValue const&) = default;
JsonValue& operator=(JsonValue &&) = default;
JsonValue( T t ):value(std::move(t)) {}
std::optional<T> value;
std::string toString() const final {
if (value)
return to_json_string(*value);
else
return "(null)";
}
bool isNull() const final {
return !static_cast<bool>(value);
}
void setNull() final {
value = std::nullopt;
}
};
template<class T>
JsonValue(T)->JsonValue<T>;
you create a free function to_json_string
for each T
you pass to JsonValue
; if you don't, you get a compile-time error.
The remaining tricky part is making a variant containing a vector of a type depending on the same variant.
struct json_variant;
using json_array = std::vector<json_variant>;
struct json_variant :
std::variant< JsonValue<double>, JsonValue<std::string>, JsonValue<json_array> >
{
using std::variant< JsonValue<double>, JsonValue<std::string>, JsonValue<json_array> >::variant;
std::variant< JsonValue<double>, JsonValue<std::string>, JsonValue<json_array> > const& base() const { return *this; }
std::variant< JsonValue<double>, JsonValue<std::string>, JsonValue<json_array> >& base() { return *this; }
};
BasicJsonType const& getInterface( json_variant const& var )
{
return std::visit( [](auto& elem)->BasicJsonType const& { return elem; }, var.base());
}
BasicJsonType& getInterface( json_variant& var )
{
return std::visit( [](auto& elem)->BasicJsonType& { return elem; }, var.base());
}
std::string to_json_string( json_array const& arr )
{
std::stringstream ss;
ss << "{";
for (auto&& elem:arr)
{
ss << getInterface(elem).toString();
ss << ",";
}
ss << "}";
return ss.str();
}
and test code:
JsonValue<json_array> bob;
bob.value.emplace();
bob.value->push_back( JsonValue(3.14) );
bob.value->push_back( JsonValue(std::string("Hello world!")) );
std::cout << bob.toString();
there we go, a value-semantics Json data type in C++.
In c++11, you can use boost::any
or boost::variant
. Everything I did above works with them, except the deduction guide (which is just syntactic sugar).
All of the alternative plans also work; a value pointer, surrendering copy and using unique ptr, a cow pointer, etc.
You can also roll your own any or variant, or find a stand-alone one, if you dislike boost.
template<class T, class=void>
struct has_clone_method:std::false_type{};
template<class T>
struct has_clone_method<T,
decltype( void(&T::clone) )
>:std::true_type{};
template<class T,
typename std::enable_if<!has_clone_method<T>{}, bool>::type = true
>
std::unique_ptr<T> do_clone( T const& t ) {
return std::make_unique<T>(t);
}
template<class T,
typename std::enable_if<has_clone_method<T>{}, bool>::type = true
>
std::unique_ptr<T> do_clone( T const& t ) {
return t.clone();
}
template<class T>
struct value_ptr:std::unique_ptr<T>
{
using base = std::unique_ptr<T>;
using base::base;
using base::operator=;
value_ptr()=default;
value_ptr(value_ptr&&)=default;
value_ptr& operator=(value_ptr&&)=default;
template<class D,
typename std::enable_if<std::is_base_of<T, D>::value, bool> = true
>
value_ptr( value_ptr<D> const& o ):
base( o?do_clone(*o):nullptr)
{}
template<class D,
typename std::enable_if<std::is_base_of<T, D>::value, bool> = true
>
value_ptr( value_ptr<D>&& o ):
base( std::move(o) )
{}
value_ptr( base b ):base(std::move(b)) {}
value_ptr(value_ptr const& o):
base( o?do_clone(*o):nullptr )
{}
value_ptr& operator=(value_ptr const& o) {
if (!o)
{
this->reset();
}
else if (this != &o) // test only needed for optimization
{
auto tmp = do_clone(*o);
swap( (base&)*this, tmp );
}
return *this;
}
};
template<class T, class...Args>
value_ptr<T> make_value_ptr( Args&&...args ) {
std::unique_ptr<T> retval( new T(std::forward<Args>(args)...) );
return std::move(retval);
}
class BasicJsonType {
public:
virtual std::string toString() const = 0;
virtual void setNull() = 0;
virtual bool isNull() const = 0;
virtual std::unique_ptr<BasicJsonType> clone() const = 0;
virtual ~BasicJsonType() = default;
};
using Json = value_ptr<BasicJsonType>;
using JsonVector = std::vector<Json>;
// your own to_json_string for the type in question
template<class T>
std::string to_json_string( T const& ) = delete;
std::string to_json_string( std::string const& s ) { return s; }
std::string to_json_string( double const& d )
{
std::stringstream ss;
ss << d;
return ss.str();
}
template <typename T>
class JsonValue:public BasicJsonType {
public:
JsonValue() = default;
JsonValue(JsonValue const&) = default;
JsonValue(JsonValue &&) = default;
JsonValue& operator=(JsonValue const&) = default;
JsonValue& operator=(JsonValue &&) = default;
JsonValue( T t ):value(make_value_ptr<T>(std::move(t))) {}
value_ptr<T> value;
std::string toString() const final {
if (value)
return to_json_string(*value);
else
return "(null)";
}
bool isNull() const final {
return !static_cast<bool>(value);
}
void setNull() final {
value = nullptr;
}
std::unique_ptr<BasicJsonType> clone() const final {
return std::unique_ptr<JsonValue>(new JsonValue(*this));
}
};
using JsonNumber = JsonValue<double>;
using JsonString = JsonValue<std::string>;
using JsonArray = JsonValue<JsonVector>;
std::string to_json_string( JsonVector const& arr )
{
std::stringstream ss;
ss << "{";
for (auto&& elem:arr)
{
if (elem)
{
ss << elem->toString();
}
ss << ",";
}
ss << "}";
return ss.str();
}
int main() {
JsonArray arr;
arr.value = make_value_ptr<JsonVector>();
arr.value->push_back( make_value_ptr<JsonNumber>( 3.14 ));
arr.value->push_back( make_value_ptr<JsonString>( "Hello World" ));
std::cout << arr.toString() << "\n";
}
here we make value_ptr
, a smart pointer that supports copy.
It uses do_clone
, which calls .clone()
if it exists, and if it does not invokes their copy constructor. This permits you to make a value_ptr<T>
where T
is a value type, or a value_ptr<T>
where T
is an abstract type with a .clone()
method.
I use it for a low-quality "optional" within JsonValue
itself (a nullable type).
A JsonVector
is then a vector of value_ptr<BasicJsonType>
.
A BasicJsonType
is implemented in JsonValue
, where it stores it data in turn in a value_ptr<T>
.
An iterative improvement would be to move the polymorphism to an internal detail.
Have a JsonValue
that stores a value_ptr
to a JsonBase
. The JsonStorage<T>
class implements JsonBase
, and is not itself nullable.
JsonValue
knows all 4 types that it can be. It provides interfaces that try-to-get the value of a specific type, and fail if you ask for the wrong type.
This reduces indirection, and gives the result that there isn't a NULL of type double, string, array that is distinct.
class JsonData {
public:
virtual std::string toString() const = 0;
virtual std::unique_ptr<JsonData> clone() const = 0;
virtual ~JsonData() = default;
};
using JsonPoly = value_ptr<JsonData>;
template<class T>
class JsonStorage:public JsonData {
public:
T value;
std::string toString() const final {
return to_json_string(value);
}
JsonStorage( T t ):value(std::move(t)) {}
JsonStorage() = default;
JsonStorage( JsonStorage const& )=default;
JsonStorage( JsonStorage && )=default;
JsonStorage& operator=( JsonStorage const& )=default;
JsonStorage& operator=( JsonStorage && )=default;
std::unique_ptr<JsonData> clone() const final {
return std::unique_ptr<JsonStorage>(new JsonStorage(*this));
}
};
struct JsonValue {
JsonValue() = default;
JsonValue( JsonValue const& ) = default;
JsonValue( JsonValue && ) = default;
JsonValue& operator=( JsonValue const& ) = default;
JsonValue& operator=( JsonValue && ) = default;
explicit operator bool() { return static_cast<bool>(data); }
std::string toString() const {
if (!data)
return "(null)";
else
return data->toString();
}
template<class T>
T* get() {
if (!data) return nullptr;
JsonStorage<T>* pValue = dynamic_cast<JsonStorage<T>*>(data.get());
if (!pValue) return nullptr;
return &(pValue->value);
}
template<class T>
T const* get() const {
if (!data) return nullptr;
JsonStorage<T> const* pValue = dynamic_cast<JsonStorage<T>*>(data.get());
if (!pValue) return nullptr;
return &(pValue->value);
}
JsonValue( double d ):
data( make_value_ptr<JsonStorage<double>>(d))
{}
JsonValue( std::string s ):
data( make_value_ptr<JsonStorage<std::string>>(s))
{}
JsonValue( char const* str ):
data( make_value_ptr<JsonStorage<std::string>>(str))
{}
JsonValue( std::initializer_list<JsonValue> );
private:
value_ptr<JsonData> data;
};
using JsonVector = std::vector<JsonValue>;
std::string to_json_string( JsonVector const& arr )
{
std::stringstream ss;
ss << "{";
for (auto&& elem:arr)
{
ss << elem.toString();
ss << ",";
}
ss << "}";
return ss.str();
}
JsonValue::JsonValue( std::initializer_list<JsonValue> il ):
data( make_value_ptr<JsonStorage<JsonVector>>( il ))
{}
int main() {
JsonValue arr = {JsonValue{3.14}, JsonValue{"Hello World"}};
std::cout << arr.toString() << "\n";
}
Here, given a JsonValue v
(not a template), you can ask v.get<double>()
which returns a pointer-to-double if and only if the value contains a double.
JsonValue v = 3.14
works, as does JsonValue str = "hello"
.
Adding new types requires a to_json_string
overload, and that the type supported be regular.
JsonValue
is a polymorphic value type. The virtual stuff is all implementation details, not exposed to the end user. We do type erasure internally. It is basically a std::any
, with an extra toString
method.