Skip to content

Instantly share code, notes, and snippets.

@IlyaSkriblovsky
Last active January 5, 2022 04:57
Show Gist options
  • Save IlyaSkriblovsky/1c60a4de593bc5a4d8c241d711a25f00 to your computer and use it in GitHub Desktop.
Save IlyaSkriblovsky/1c60a4de593bc5a4d8c241d711a25f00 to your computer and use it in GitHub Desktop.
Typing to implement dynamic Variant type in C++
#include <cstdio>
#include "Variant.h"
using namespace std;
class Shape
{
public:
virtual void hello() const = 0;
~Shape()
{
printf("~Shape()\n");
}
};
class Circle: public Shape
{
public:
int r;
Circle(int r): r(r)
{
printf("Circle(%d)\n", r);
}
Circle(const Circle& other): r(other.r)
{
printf("Circle(copy %d)\n", r);
}
virtual ~Circle()
{
printf("~Circle()\n");
}
void hello() const
{
printf("I'm circle r=%d\n", r);
}
};
class Rectangle: public Shape
{
public:
int x, y;
Rectangle(int x, int y): x(x), y(y)
{
printf("Rectangle(%d, %d)\n", x, y);
}
Rectangle(const Rectangle& other): x(other.x), y(other.y)
{
printf("Rectangle(copy %d, %d)\n", x, y);
}
~Rectangle()
{
printf("~Rectangle()\n");
}
void hello() const
{
printf("I'm rectangle x=%d y=%d\n", x, y);
}
};
int main() {
Circle circle(42);
Variant v1(circle);
Variant v2;
v2 = Rectangle(5, 7);
v2 = v1;
v1.get<Circle>().hello(); // "I'm circle r=42"
v2.get<Rectangle>().hello(); // "Type mismatch: Variant holds Circle, but get<Rectnagle> is called"
Variant v3;
v3.get<Rectangle>().hello(); // "Variant is empty, but get<Rectangle> is called"
}
#ifndef __VARIANT_H
#define __VARIANT_H
#include <cstdio>
#include <typeinfo>
#include <map>
typedef void (*deleter)(void*);
typedef void* (*copier)(const void*);
template <typename T>
void delete_type(void *ptr)
{
delete reinterpret_cast<T*>(ptr);
}
template <typename T>
void* copy_type(const void *src)
{
return new T(*reinterpret_cast<const T*>(src));
}
// Holder for std::type_info since it is not copy-assignable
class TypeIDHolder
{
public:
TypeIDHolder(): type(&typeid(void)) { }
TypeIDHolder(const std::type_info& type): type(&type) { }
const std::type_info& get_type_info() const { return *type; }
bool operator < (const TypeIDHolder& other) const { return type->before(*other.type); }
bool operator == (const TypeIDHolder& other) const { return *type == *other.type; }
bool operator != (const TypeIDHolder& other) const { return *type != *other.type; }
private:
const std::type_info* type;
};
class Variant
{
public:
Variant(): ptr(0) { }
template <typename T>
Variant(const T& value)
{
set(value);
}
~Variant()
{
clear();
}
template <typename T>
void set(const T& value)
{
ptr = new T(value);
type = TypeIDHolder(typeid(T));
deleters[typeid(T)] = &delete_type<T>;
copiers[typeid(T)] = &copy_type<T>;
}
template <typename T>
const T& get() const
{
if (! ptr)
{
fprintf(stderr, "Variant is empty, but get<%s> is called\n", typeid(T).name());
abort();
}
if (type != typeid(T))
{
fprintf(stderr, "Type mismatch: Variant holds %s, but get<%s> is called\n",
type.get_type_info().name(), typeid(T).name());
abort();
}
return *reinterpret_cast<const T*>(ptr);
}
void clear()
{
if (ptr)
{
deleters[type](ptr);
ptr = 0;
}
}
template <typename T>
void operator = (const T& value)
{
clear();
set(value);
}
void operator = (const Variant& other)
{
clear();
ptr = copiers[other.type](other.ptr);
type = other.type;
}
private:
void* ptr;
TypeIDHolder type;
static std::map<TypeIDHolder, deleter> deleters;
static std::map<TypeIDHolder, copier> copiers;
};
std::map<TypeIDHolder, deleter> Variant::deleters;
std::map<TypeIDHolder, copier> Variant::copiers;
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment