That is not actually the way to do it.
There are two typical ways to implement something akin to dynamic typing in C++:
- the Object-Oriented way: a class hierarchy and the
Visitor
pattern - the Functional-Programming way: a tagged union
The latter is rather simple using boost::variant
, the former is well documented on the web. I would personally recommend boost::variant
to start with.
If you want to go down the full dynamic typing road, then things get trickier. In dynamic typing, an object is generally represented as a dictionary containing both other objects and functions, and a function takes a list/dictionary of objects and returns a list/dictionary of objects. Modelling it in C++ is feasible, but it'll be wordy...
How is an object represented in a dynamically typed language ?
The more generic representation is for the language to represent an object as both a set of values (usually named) and a set of methods (named as well). A simplified representation looks like:
struct Object {
using ObjectPtr = std::shared_ptr<Object>;
using ObjectList = std::vector<ObjectPtr>;
using Method = std::function<ObjectList(ObjectList const&)>;
std::map<std::string, ObjectPtr> values;
std::map<std::string, Method> methods;
};
If we take Python as an example, we realize we are missing a couple things:
- We cannot implement
getattr
for example, becauseObjectPtr
is a different type fromMethod
- This is a recursive implementation, but without the basis: we are lacking innate types (typically
Bool
,Integer
,String
, ...)
Dealing with the first issue is relatively easy, we transform our object to be able to become callable:
class Object {
public:
using ObjectPtr = std::shared_ptr<Object>;
using ObjectList = std::vector<ObjectPtr>;
using Method = std::function<ObjectList(ObjectList const&)>;
virtual ~Object() {}
//
// Attributes
//
virtual bool hasattr(std::string const& name) {
throw std::runtime_error("hasattr not implemented");
}
virtual ObjectPtr getattr(std::string const&) {
throw std::runtime_error("gettattr not implemented");
}
virtual void setattr(std::string const&, ObjectPtr) {
throw std::runtime_error("settattr not implemented");
}
//
// Callable
//
virtual ObjectList call(ObjectList const&) {
throw std::runtime_error("call not implemented");
}
virtual void setcall(Method) {
throw std::runtime_error("setcall not implemented");
}
}; // class Object
class GenericObject: public Object {
public:
//
// Attributes
//
virtual bool hasattr(std::string const& name) override {
return values.count(name) > 0;
}
virtual ObjectPtr getattr(std::string const& name) override {
auto const it = values.find(name);
if (it == values.end) {
throw std::runtime_error("Unknown attribute");
}
return it->second;
}
virtual void setattr(std::string const& name, ObjectPtr object) override {
values[name] = std::move(object);
}
//
// Callable
//
virtual ObjectList call(ObjectList const& arguments) override {
if (not method) { throw std::runtime_error("call not implemented"); }
return method(arguments);
}
virtual void setcall(Method m) {
method = std::move(m);
}
private:
std::map<std::string, ObjectPtr> values;
Method method;
}; // class GenericObject
And dealing with the second issue requires seeding the recursion:
class BoolObject final: public Object {
public:
static BoolObject const True = BoolObject{true};
static BoolObject const False = BoolObject{false};
bool value;
}; // class BoolObject
class IntegerObject final: public Object {
public:
int value;
}; // class IntegerObject
class StringObject final: public Object {
public:
std::string value;
}; // class StringObject
And now you need to add capabilities, such as value comparison.