I was expecting the assignment operator to be dynamically bound depending on the argument type:
struct NumberObject;
struct NumberString;
struct DataType
{
virtual void operator=(DataType& rhs) {};
virtual void operator=(NumberString& rhs) {};
};
struct NumberObject : DataType
{
double data = 7;
void operator=(DataType& rhs) override
{
std::cout << "Copying from DataType\n";
// The assignment operator that's called is this one. I was hoping it would be the one below.
}
void operator=(NumberString& rhs) override
{
std::cout << "Copying from NumberString\n";
}
};
struct NumberString : DataType
{
std::string data = "7";
void operator=(NumberString& rhs) override{};
};
int main()
{
DataType *pDataType1, *pDataType2;
pDataType1 = new NumberObject;
pDataType2 = new NumberString;
*pDataType1 = *pDataType2; // Both pointers are to DataType, I was hoping that the assignment operator taking
// NumberString as the right hand side would be called
return 0;
}
From this I guess that C++ can't dynamically bind functions based on the argument type. The dynamic binding only happens when calling a function directly through an object. So I thought I could do this, but it does seem like a roundabout way:
struct NumberObject : DataType
{
double data = 7;
void operator=(DataType& rhs) override
{
std::cout << "Copying from DataType\n";
rhs.assignTo(*this);
}
void operator=(NumberString& rhs) override
{
std::cout << "Copying from NumberString\n";
}
};
struct NumberString : DataType
{
std::string data = "7";
void assignTo(NumberObject& lhs) override
{// Provided the base class had this
lhs.data = atof(this->data.c_str());
}
void operator=(NumberString& rhs) override{};
};
This seems like a hack, and seeing as though the language is capable of dynamic binding, isn't this something it could do easily? Is it a weakness of the language? Or does nobody care about it?
With double dispatching, it would be:
struct NumberObject;
struct NumberString;
struct DataType
{
virtual ~DataType() = default;
virtual void operator=(const DataType&) = 0;
virtual void assignTo(NumberObject&) const = 0;
virtual void assignTo(NumberString&) const = 0;
};
struct NumberObject : DataType
{
double data = 7;
void operator=(const DataType& rhs) override { rhs.assignTo(*this); }
void assignTo(NumberObject& rhs) const override
{
std::cout << "Copying from NumberObject\n";
}
void assignTo(NumberString& rhs) const override
{
std::cout << "Copying from NumberObject\n";
}
};
struct NumberString : DataType
{
std::string data = "7";
void operator=(const DataType& rhs) override { rhs.assignTo(*this); }
void assignTo(NumberObject& rhs) const override
{
std::cout << "Copying from NumberString\n";
}
void assignTo(NumberString& rhs) const override
{
std::cout << "Copying from NumberString\n";
}
};
Demo
Related
I have a parent DataType class from which I inherit Data Type Int, DataType Double, DataTypeEnum
and CDataTypeStruct. Somewhere I use the print () method defined by the parent and somewhere I rewrite it. I call the print method using the << operator.
Why, when I call a title for the CDataTypeEnum type, everything is displayed correctly, as I have the print defined in the CDaraTypeEnum.
I get this
struct {
int int enum}
but if I want to list cout << structure << endl; so for the CDataTypeStruct type, I don't get an overloaded print method for each object?
Just to make the statement look like this
struct {
int int
enum {
NEW,
FIXED,
BROKEN,
DEAD
}
}
--
All program https://onecompiler.com/cpp/3y2rhbm7a and here's a minimal reproducible example:
#include <iostream>
#include <list>
#include <memory>
#include <set>
#include <string>
#include <unordered_set>
#include <vector>
using namespace std;
class CDataType
{
public:
CDataType(string type, size_t size);
friend ostream& operator << (ostream &os, CDataType &x);
virtual ostream& print (ostream &os) const;
protected:
string m_Type;
size_t m_Size;
};
CDataType::CDataType(string type, size_t size)
: m_Type(type),
m_Size(size)
{
}
ostream& operator << (ostream &os, CDataType &x)
{
x.print(os);
return os;
}
ostream& CDataType::print (ostream &os) const
{
os << m_Type;
return os;
}
class CDataTypeInt : public CDataType
{
public:
CDataTypeInt();
};
CDataTypeInt::CDataTypeInt()
: CDataType("int", 4)
{
}
class CDataTypeEnum : public CDataType
{
public:
CDataTypeEnum();
CDataTypeEnum& add(string x);
virtual ostream& print (ostream &os) const;
protected:
vector<string> listEnums;
set<string> listEnumsNames;
};
CDataTypeEnum::CDataTypeEnum()
: CDataType("enum", 4)
{
}
ostream& CDataTypeEnum::print(ostream &os) const
{
os << m_Type << "{\n";
for (auto i=listEnums.begin(); i != listEnums.end(); ++i )
{
os << *i;
if(i != listEnums.end()-1)
{
os << ",";
}
os << "\n";
}
os << "}";
return os;
}
CDataTypeEnum& CDataTypeEnum::add(string x)
{
if(listEnumsNames.find(x) == listEnumsNames.end())
{
listEnums.push_back(x);
listEnumsNames.emplace(x);
}
else
cout << "vyjimkaa" << endl;
// CSyntaxException e("Duplicate enum value: " + x);
return *this;
}
class CDataTypeStruct : public CDataType
{
public:
virtual ostream& print (ostream &os) const;
CDataTypeStruct();
CDataTypeStruct& addField(const string &name, const CDataType &type);
protected:
list<unique_ptr<CDataType>> m_Field;
unordered_set<string> m_Field_names;
};
CDataTypeStruct::CDataTypeStruct()
:CDataType("struct", 0)
{
}
CDataTypeStruct& CDataTypeStruct::addField(const string &name, const CDataType &type)
{
if( m_Field_names.find(name) == m_Field_names.end() )
{
m_Field.push_back(make_unique<CDataType>(type));
m_Field_names.emplace(name);
}
// else
//throw CSyntaxException("Duplicate field: " + name);
return *this;
}
ostream& CDataTypeStruct::print (ostream &os) const
{
os << m_Type << "{\n";
for(const auto &uptr : m_Field)
{
uptr->print(os) << " " /*<< "{\n"*/;
}
os << "}";
return os;
}
int main()
{
CDataTypeInt inta;
CDataTypeInt intb;
CDataTypeStruct struktura;
CDataTypeEnum enumos;
enumos.add( "NEW" ).add ( "FIXED" ) .add ( "BROKEN" ) .add ( "DEAD" );
struktura.addField("integera", inta);
struktura.addField("integerb", intb);
struktura.addField("bbb", enumos);
cout << enumos << endl;
cout << struktura << endl;
}```
As I already suggested in a similar question you posted already (and deleted), you're again slicing here:
struktura.addField("integera", inta); // Slicing
struktura.addField("integerb", intb); // Slicing
struktura.addField("bbb", enumos); // Slicing
Again, consider following guideline "A polymorphic class should suppress public copy/move" from the C++ Core Guidelines. To do this:
class CDataType {
public:
// ...
// Disable move and copy
CDataType(CDataType const &) = delete;
CDataType(CDataType &&) = delete;
CDataType& operaror=(CDataType const &) = delete;
CDataType& operaror=(CDataType &&) = delete;
// Dtor should be virtual.
virtual ~CDataType() = default;
// ...
};
Then, adapt your code accordingly (as it won't compile anymore).
Also, The destructor of CDataType should be virtual.
Edit: Please consider the following example, which hopeful makes the slicing issue clearer:
#include <cstdio>
#include <list>
#include <memory>
struct A {
A() = default;
A(A const&) { std::puts("A(A const&)"); }
virtual ~A() { std::puts("~A()"); }
};
struct B : public A {
B() = default;
B(B const&) { std::puts("B(B const&)"); }
~B() override { std::puts("~B()"); }
};
void slicing_addField(A const& a) {
std::puts("slicing_f");
std::list<std::unique_ptr<A>> l;
l.push_back(std::make_unique<A>(a));
}
void non_slicing_addField(std::unique_ptr<A> a) {
std::puts("non_slicing_f");
std::list<std::unique_ptr<A>> l;
l.push_back(std::move(a));
}
int main() {
// This is what you do
slicing_addField(B{});
// This is how you may solve
non_slicing_addField(std::make_unique<B>());
}
Output:
slicing_f
A(A const&)
~A()
~B()
~A()
non_slicing_f
~B()
~A()
As you can see from the output, you're only calling A(A const&) in slicing_addField.
This means the call to make_unique<A>(a) is allocating an object of run-time type A (while you want it of run-time type B).
The main problem is that you try to copy the CDataType decendant in addField by using make_unique<CDataType>, but that actually creates a CDataType, not a CDataTypeInt, CDataTypeEnum or CDataTypeStruct.
Since constructors can't be virtual (in standard C++) you need to create a separate virtual function to do the copying. A common name for such a function is clone. Example:
class CDataType {
public:
CDataType(string type, size_t size);
CDataType(const CDataType&) = delete;
CDataType& operator=(const CDataType&) = delete;
virtual ~CDataType() = default; // add virtual dtor
virtual std::unique_ptr<CDataType> clone() const {
return std::make_unique<CDataType>(m_Type, m_Size);
}
// ...
};
class CDataTypeEnum : public CDataType {
public:
using CDataType::CDataType;
std::unique_ptr<CDataType> clone() const override {
// use make_unique to create the correct type:
auto np = std::make_unique<CDataTypeEnum>(m_Type, m_Size);
np->listEnums = listEnums;
np->listEnumsNames = listEnumsNames;
return np;
}
// ...
};
class CDataTypeStruct : public CDataType {
public:
using CDataType::CDataType;
std::unique_ptr<CDataType> clone() const override {
// use make_unique to create the correct type:
auto np = std::make_unique<CDataTypeStruct>(m_Type, m_Size);
np->m_Field_names = m_Field_names;
for(auto& cdtp : m_Field)
np->m_Field.emplace_back(cdtp->clone()); // use clone here
return np;
}
// ...
};
Then addField would look like this:
CDataTypeStruct& CDataTypeStruct::addField(const string& name,
const CDataType& type) {
if (m_Field_names.find(name) == m_Field_names.end()) {
m_Field.emplace_back(type.clone()); // and use clone here
m_Field_names.emplace(name);
}
return *this;
}
I have a template:
template<typename T>
struct Parameter {
T value;
std::string name;
Parameter(std::string name, T value) : name(name), value(value){}
void fix() {
// Fix this->value (make this->value const)
}
void print() { std::cout << value << std::endl; }
};
and I would like at some point after initialization to 'const-ify' the value variable
std::string name = "variance";
double var = 1.0;
Parameter<double> variance(name, var);
variance.print();
variance.fix();
variance.value = 2.3; // Not Allowed, throws error
Is it possible to do so and how?
If you want to maintain the same interface, and marshalling access to value through accessors is something you want to avoid, then you could isolate the "fixable" feature in its own dedicated type that implicitly converts to/from T:
template<typename T>
class fixable {
bool fixed_ = false;
T val_;
public:
fixable() = default;
fixable(T v) : val_(v) {}
fixable(const fixable&) = default;
fixable(fixable&&) = default;
operator const T&() const {
return val_;
}
fixable& operator=(const T& v) {
if(fixed_ ) {
throw std::runtime_error("Fixable has been fixed");
}
val_ = v;
return *this;
}
void fix() {
fixed_ = true;
}
};
You would then replace the T member with a fixable<T> within Parameter:
template<typename T>
struct Parameter {
fixable<T> value;
std::string name;
Parameter(std::string name, T value) : name(name), value(value){}
void fix() {
value.fix();
}
void print() { std::cout << value << std::endl; }
};
The main function from your question can remain exactly as-is.
You can use something like this:
Similar to abowe answer but with boolean inside the Parameter struct
template<typename T>
struct Parameter {
Parameter(std::string name, T value) : name(name), value(value), bFixed(false) {}
void fix() {
bFixed = true;
}
void print() { std::cout << value << std::endl; }
Parameter& operator=(const T& oValue)
{
if (bFixed)
{
throw std::runtime_error("Error fixed value..");
}
value = oValue;
return *this;
}
std::string name;
private:
bool bFixed;
T value;
};
int main()
{
std::string name = "variance";
double var = 1.0;
Parameter<double> variance(name, var);
variance.print();
variance.fix();
variance = 2.3; // Not Allowed, throws error
}
You cannot change a member variable from const to non-const. However, you can create a new object in which it is const. For example:
template<typename T>
struct example {
T value;
example<T const> fix() && {
return {value};
}
};
int main(){
auto x = example<int>{1};
x.value = 4; // OK
auto y = std::move(x).fix();
y.value = 7; // error: assignment of read-only member
}
The presence of && forces the use of std::move which makes it obvious that x should no longer be used.
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++.
Live example.
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";
}
Live example.
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.
Is there any way to force to only allow const instances of class to be instantiated, and have non-const instances be detected as an error by the compiler?
is there any generic way to take an existing class, and "constify" it by removing all non-const functionality?
One possible workaround is to create a wrapper class that holds an instance of the class and only gives access to a const reference to it.
template<class T>
class Immutable {
public:
template<typename... Args>
Immutable(Args&&... args) : instance(forward<Args>(args)...) {
}
operator const T&() {
return instance;
}
const T& get() const {
return instance;
}
private:
Immutable& operator=(const Immutable& other) = delete;
T instance;
};
Suppose you have a mutable class Class:
class Class {
public:
Class() : m_value(0) {
}
Class(const Class& other) : m_value(other.m_value) {
}
Class(int value) : m_value(value) {
}
Class(int x, int y) : m_value(x + y) {
}
void change(int value) {
m_value = value;
}
int value() const {
return m_value;
}
private:
int m_value;
};
Here is how Immutable<Class> can be used:
void functionTakingConstReference(const Class& x) {
}
void functionTakingNonConstReference(Class& x) {
}
void functionTakingImmutableClass(Immutable<Class>& x) {
}
void functionTakingValue(Class x) {
}
int main(int argc, char *argv[])
{
// Any constructor of Class can also be used with Immutable<Class>.
Immutable<Class> a;
Immutable<Class> b(1);
Immutable<Class> c(2, 3);
Immutable<Class> d(c);
// Compiles and works as expected.
functionTakingConstReference(a);
functionTakingImmutableClass(a);
functionTakingValue(a);
cout << a.get().value() << endl;
// Doesn't compile because operator= is deleted.
// b = a;
// Doesn't compile because "change" is a non-const method.
// a.get().change(4);
// Doesn't compile because the function takes a non-const reference to Class as an argument.
// functionTakingNonConstReference(a);
return 0;
}
Is there any way to force to only allow const instances of class to be instantiated, and have non-const instances be detected as an error by the compiler?
No.
But you can declare all members as const. Then both const and non-const instances would behave largely in the same way and it shouldn't matter whether the instances are const.
I think you are looking for an immutable class. An easy way to get immutability is to declare all member variables as const. This way you ensure that the state of your objects will not change after construction.
This guarantee is independent of whether your object is const and even if you have non-const member functions for that class.
For example:
class Foo
{
public:
Foo(int id, double value) : m_id(id), m_value(value) { }
int Id() const { return m_id; }
double Value() const { return m_value; }
private:
const int m_id;
const double m_value;
};
Another way you could generate an immutable object of any type is through a template class. Something like this:
class Bar
{
public:
Bar(int id, double value) : m_id(id), m_value(value) { }
int Id() const { return m_id; }
double Value() const { return m_value; }
void SetId(int id) { m_id = id; }
private:
int m_id;
double m_value;
};
template<typename T>
class MyImmutable
{
public:
const T m_obj;
MyImmutable(const T& obj) : m_obj(obj)
{ }
};
int main()
{
cout << "Hello World!" << endl;
Foo a(1,2.0);
Bar x(2,3.0);
MyImmutable<Bar> y(x);
cout << "a.id = " << a.Id() << endl;
cout << "a.value = " << a.Value() << endl;
cout << "y.id = " << y.m_obj.Id() << endl;
cout << "y.value = " << y.m_obj.Value() << endl;
y.m_obj.SetId(2); // calling non-const member throws an error.
return 0;
}
Basically i just want to do an arbitrary operation using given arguments of arbitrary types.
Argument type base class is Var, and Operation is base class of the operation that will executed for given arguments.
I have Evaluator class, that hold a collection of operators which mapped using opId. Evaluator will do operation based on opId argument given in evaluate() member function, then evaluate() function will do search for supported operator that will accept argument type and opId.
what I want to ask is, is there any efficient pattern or algorithm that will do this without dynamic_cast<> and/or looping through operator collection.
`
class Var {
public:
bool isValidVar();
static Var invalidVar();
}
template<typename T> class VarT : public Var {
public:
virtual const T getValue() const;
}
class Operator {
public:
virtual Var evaluate(const Var& a, const Var& b) = 0;
}
template<typename T> class AddOperator : public Operator {
public:
virtual Var evaluate(const Var& a, const Var& b)
{ //dynamic_cast is slow!
const VarT<T>* varA = dynamic_cast<const VarT<T>*>(&a);
const VarT<T>* varB = dynamic_cast<const VarT<T>*>(&b);
if(varA && varB) //operation supported
{
return VarT<T>(varA->getValue() + varA->getValue());
}
return Var::invalidVar(); //operation for this type is not supported
}
}
class Evaluator {
private:
std::map<int,std::vector<Operator>> operatorMap;
public:
virtual Var evaluate(const Var& a, const Var& b,int opId)
{
std::map<int,std::vector<Operator>>::iterator it = this->operatorMap.find(opId);
if(it != this->operatorMap.end())
{
for(size_t i=0 ; i<it->second.size() ; i++)
{
Var result = it->second.at(i).evaluate(a,b);
if(result.isValidVar())
{
return result;
}
}
}
//no operator mapped, or no operator support the type
return Var::invalidVar();
}
}
`
if you do not want to use dynamic_cast, consider adding type traits into your design.
Added 05/03/10 : The following sample will demonstrate how runtime-traits works
CommonHeader.h
#ifndef GENERIC_HEADER_INCLUDED
#define GENERIC_HEADER_INCLUDED
#include <map>
#include <vector>
#include <iostream>
// Default template
template <class T>
struct type_traits
{
static const int typeId = 0;
static const int getId() { return typeId; }
};
class Var
{
public:
virtual ~Var() {}
virtual int getType() const = 0;
virtual void print() const = 0;
};
template<typename T>
class VarT : public Var
{
T value;
public:
VarT(const T& v): value(v) {}
virtual int getType() const { return type_traits<T>::getId(); };
virtual void print() const { std::cout << value << std::endl; };
const T& getValue() const { return value; }
};
class Operator
{
public:
virtual ~Operator() {}
virtual Var* evaluate(const Var& a, const Var& b) const = 0;
};
template<typename T>
class AddOperator : public Operator
{
public:
virtual Var* evaluate(const Var& a, const Var& b) const
{
// Very basic condition guarding
// Allow operation within similar type only
// else have to create additional compatibility checker
// ie. AddOperator<Matrix> for Matrix & int
// it will also requires complicated value retrieving mechanism
// as static_cast no longer can be used due to unknown type.
if ( (a.getType() == b.getType()) &&
(a.getType() == type_traits<T>::getId()) &&
(b.getType() != type_traits<void>::getId()) )
{
const VarT<T>* varA = static_cast<const VarT<T>*>(&a);
const VarT<T>* varB = static_cast<const VarT<T>*>(&b);
return new VarT<T>(varA->getValue() + varB->getValue());
}
return 0;
}
};
class Evaluator {
private:
std::map<int, std::vector<Operator*>> operatorMap;
public:
void registerOperator(Operator* pOperator, int iCategory)
{
operatorMap[iCategory].push_back( pOperator );
}
virtual Var* evaluate(const Var& a, const Var& b, int opId)
{
Var* pResult = 0;
std::vector<Operator*>& opList = operatorMap.find(opId)->second;
for ( std::vector<Operator*>::const_iterator opIter = opList.begin();
opIter != opList.end();
opIter++ )
{
pResult = (*opIter)->evaluate( a, b );
if (pResult)
break;
}
return pResult;
}
};
#endif
DataProvider header
#ifdef OBJECTA_EXPORTS
#define OBJECTA_API __declspec(dllexport)
#else
#define OBJECTA_API __declspec(dllimport)
#endif
// This is the "common" header
#include "CommonHeader.h"
class CFraction
{
public:
CFraction(void);
CFraction(int iNum, int iDenom);
CFraction(const CFraction& src);
int m_iNum;
int m_iDenom;
};
extern "C" OBJECTA_API Operator* createOperator();
extern "C" OBJECTA_API Var* createVar();
DataProvider implementation
#include "Fraction.h"
// user-type specialization
template<>
struct type_traits<CFraction>
{
static const int typeId = 10;
static const int getId() { return typeId; }
};
std::ostream& operator<<(std::ostream& os, const CFraction& data)
{
return os << "Numerator : " << data.m_iNum << " # Denominator : " << data.m_iDenom << std::endl;
}
CFraction operator+(const CFraction& lhs, const CFraction& rhs)
{
CFraction obj;
obj.m_iNum = (lhs.m_iNum * rhs.m_iDenom) + (rhs.m_iNum * lhs.m_iDenom);
obj.m_iDenom = lhs.m_iDenom * rhs.m_iDenom;
return obj;
}
OBJECTA_API Operator* createOperator(void)
{
return new AddOperator<CFraction>;
}
OBJECTA_API Var* createVar(void)
{
return new VarT<CFraction>( CFraction(1,4) );
}
CFraction::CFraction() :
m_iNum (0),
m_iDenom (0)
{
}
CFraction::CFraction(int iNum, int iDenom) :
m_iNum (iNum),
m_iDenom (iDenom)
{
}
CFraction::CFraction(const CFraction& src) :
m_iNum (src.m_iNum),
m_iDenom (src.m_iDenom)
{
}
DataConsumer
#include "CommonHeader.h"
#include "windows.h"
// user-type specialization
template<>
struct type_traits<int>
{
static const int typeId = 1;
static const int getId() { return typeId; }
};
int main()
{
Evaluator e;
HMODULE hModuleA = LoadLibrary( "ObjectA.dll" );
if (hModuleA)
{
FARPROC pnProcOp = GetProcAddress(hModuleA, "createOperator");
FARPROC pnProcVar = GetProcAddress(hModuleA, "createVar");
// Prepare function pointer
typedef Operator* (*FACTORYOP)();
typedef Var* (*FACTORYVAR)();
FACTORYOP fnCreateOp = reinterpret_cast<FACTORYOP>(pnProcOp);
FACTORYVAR fnCreateVar = reinterpret_cast<FACTORYVAR>(pnProcVar);
// Create object
Operator* pOp = fnCreateOp();
Var* pVar = fnCreateVar();
AddOperator<int> intOp;
AddOperator<double> doubleOp;
e.registerOperator( &intOp, 0 );
e.registerOperator( &doubleOp, 0 );
e.registerOperator( pOp, 0 );
VarT<int> i1(10);
VarT<double> d1(2.5);
VarT<float> f1(1.0f);
std::cout << "Int Obj id : " << i1.getType() << std::endl;
std::cout << "Double Obj id : " << d1.getType() << std::endl;
std::cout << "Float Obj id : " << f1.getType() << std::endl;
std::cout << "Import Obj id : " << pVar->getType() << std::endl;
Var* i_result = e.evaluate(i1, i1, 0); // result = 20
Var* d_result = e.evaluate(d1, d1, 0); // no result
Var* f_result = e.evaluate(f1, f1, 0); // no result
Var* obj_result = e.evaluate(*pVar, *pVar, 0); // result depend on data provider
Var* mixed_result1 = e.evaluate(f1, d1, 0); // no result
Var* mixed_result2 = e.evaluate(*pVar, i1, 0); // no result
obj_result->print();
FreeLibrary( hModuleA );
}
return 0;
}
If you can modify the type Var you could add type-Ids for the argument types. But in the implementation of your operations you would always have to use a dynamic_cast at some point. If your types and operations are fixed at compile-time, you can do the whole thing with templates using Boost.MPL (specifically the containers).
Your sample code contains many errors, including slicing problems.
I'm not 100% sure, but I seem to remember you can use const type_info* as a key for a map.
If so, you could use something like following. It is not free from RTTI (type_info), but since Evaluator already checks the typeids, you can use a static_cast instead of a dynamic_cast (but it isn't that important now that the code doesn't blindly search for the right operator to apply).
Of course, the following is completely broken in terms of memory management. Reimplement with smart pointers of your choice.
#include <map>
#include <typeinfo>
#include <cassert>
#include <iostream>
struct CompareTypeinfo
{
bool operator()(const std::type_info* a, const std::type_info* b) const
{
return a->before(*b);
}
};
class Var {
public:
virtual ~Var() {}
virtual const std::type_info& getType() const = 0;
virtual void print() const = 0;
};
template<typename T> class VarT : public Var {
T value;
public:
VarT(const T& v): value(v) {}
const T& getValue() const { return value; }
virtual const std::type_info& getType() const { return typeid(T); }
virtual void print() const { std::cout << value << '\n'; }
};
class Operator {
public:
virtual ~Operator() {}
virtual Var* evaluate(const Var& a, const Var& b) const = 0;
virtual const std::type_info& getType() const = 0;
};
template<typename T> class AddOperator : public Operator {
public:
typedef T type;
virtual const std::type_info& getType() const { return typeid(T); }
virtual Var* evaluate(const Var& a, const Var& b) const
{
//it is the responsibility of Evaluator to make sure that the types match the operator
const VarT<T>* varA = static_cast<const VarT<T>*>(&a);
const VarT<T>* varB = static_cast<const VarT<T>*>(&b);
return new VarT<T>(varA->getValue() + varB->getValue());
}
};
class Evaluator {
private:
typedef std::map<const std::type_info*, Operator*, CompareTypeinfo> TypedOpMap;
typedef std::map<int, TypedOpMap> OpMap;
OpMap operatorMap;
public:
template <class Op>
void registerOperator(int opId)
{
operatorMap[opId].insert(std::make_pair(&typeid(typename Op::type), new Op));
}
Var* evaluate(const Var& a, const Var& b,int opId)
{
OpMap::const_iterator op = operatorMap.find(opId);
if (op != operatorMap.end() && a.getType() == b.getType()) {
TypedOpMap::const_iterator typed_op = op->second.find(&a.getType());
if (typed_op != op->second.end()) {
//double-checked
assert(typed_op->second->getType() == a.getType());
return typed_op->second->evaluate(a, b);
}
}
return 0;
}
};
int main()
{
Evaluator e;
e.registerOperator<AddOperator<int> >(0);
e.registerOperator<AddOperator<double> >(0);
VarT<int> i1(10), i2(20);
VarT<double> d1(2.5), d2(1.5);
VarT<float> f1(1.0), f2(2.0);
Var* i_result = e.evaluate(i1, i2, 0);
Var* d_result = e.evaluate(d1, d2, 0);
Var* f_result = e.evaluate(f1, f2, 0);
Var* mixed_result = e.evaluate(i1, d2, 0);
assert(i_result != 0);
assert(d_result != 0);
assert(f_result == 0); //addition not defined for floats in Evaluator
assert(mixed_result == 0); //and never for mixed types
i_result->print(); //30
d_result->print(); //4.0
}