Skip to content

Instantly share code, notes, and snippets.

@nclettiere
Last active April 2, 2025 12:38
Show Gist options
  • Select an option

  • Save nclettiere/3177b02f2a5b35745c8d35c8fb6750cb to your computer and use it in GitHub Desktop.

Select an option

Save nclettiere/3177b02f2a5b35745c8d35c8fb6750cb to your computer and use it in GitHub Desktop.
Tiny portable C++ Variant class based on the Godot Engine Variant class
#include <string>
#include <cassert>
#include <cmath>
#include <iostream>
using String = std::string;
class Variant
{
public:
enum Type
{
NIL,
// atomic types
BOOL,
INT,
FLOAT,
STRING,
// add custom types here ...
// internal
COUNT
};
private:
Type type = NIL;
union
{
bool _bool;
int64_t _int;
double _float;
String* _string; // Pointer for dynamic allocation
} data;
void clear();
public:
static String get_type_name(Variant::Type p_type);
Variant() :
type(NIL) {}
Variant(bool value);
Variant(int64_t p_int64);
Variant(int32_t p_int32);
Variant(int16_t p_int16);
Variant(int8_t p_int8);
Variant(uint64_t p_uint64);
Variant(uint32_t p_uint32);
Variant(uint16_t p_uint16);
Variant(uint8_t p_uint8);
Variant(float p_float);
Variant(double p_double);
Variant(const String& value);
Variant(const char* p_cstr);
Variant(const Variant& other);
Variant& operator=(const Variant& other);
~Variant();
Type get_type() const { return type; }
bool operator==(const Variant& other) const;
bool is_zero() const;
bool booleanize() const;
operator bool() const;
operator int64_t() const;
operator int32_t() const;
operator int16_t() const;
operator int8_t() const;
operator uint64_t() const;
operator uint32_t() const;
operator uint16_t() const;
operator uint8_t() const;
operator char32_t() const;
operator float() const;
operator double() const;
operator String() const;
private:
String stringify(int recursion_count) const;
template <typename T>
T _to_int() const
{
switch (get_type())
{
case NIL:
return 0;
case BOOL:
return data._bool ? 1 : 0;
case INT:
return T(data._int);
case FLOAT:
return T(data._float);
//case STRING:
// return reinterpret_cast<const String*>(_data._mem)->to_int();
default:
{
return 0;
}
}
}
template <typename T>
T _to_float() const
{
switch (type)
{
case NIL:
return 0;
case BOOL:
return data._bool ? 1 : 0;
case INT:
return T(data._int);
case FLOAT:
return T(data._float);
//case STRING:
// TODO
default:
{
return 0;
}
}
}
};
Variant::Variant(bool p_bool) :
type(BOOL)
{
data._bool = p_bool;
}
Variant::Variant(int64_t p_int64) :
type(INT)
{
data._int = p_int64;
}
Variant::Variant(int32_t p_int32) :
type(INT)
{
data._int = p_int32;
}
Variant::Variant(int16_t p_int16) :
type(INT)
{
data._int = p_int16;
}
Variant::Variant(int8_t p_int8) :
type(INT)
{
data._int = p_int8;
}
Variant::Variant(uint64_t p_uint64) :
type(INT)
{
data._int = int64_t(p_uint64);
}
Variant::Variant(uint32_t p_uint32) :
type(INT)
{
data._int = int64_t(p_uint32);
}
Variant::Variant(uint16_t p_uint16) :
type(INT)
{
data._int = int64_t(p_uint16);
}
Variant::Variant(uint8_t p_uint8) :
type(INT)
{
data._int = int64_t(p_uint8);
}
Variant::Variant(float p_float) :
type(FLOAT)
{
data._float = p_float;
}
Variant::Variant(double p_double) :
type(FLOAT)
{
data._float = p_double;
}
Variant::Variant(const String& p_string) :
type(STRING)
{
data._string = new std::string(p_string);
}
Variant::Variant(const char* p_cstr) :
Variant(String(p_cstr))
{}
Variant::Variant(const Variant& other) :
type(Type::NIL)
{
*this = other;
}
Variant& Variant::operator=(const Variant& other)
{
if (this != &other)
{
clear();
type = other.type;
switch (type)
{
case Type::NIL: break;
case Type::BOOL: data._bool = other.data._bool; break;
case Type::INT: data._int = other.data._int; break;
case Type::FLOAT: data._float = other.data._float; break;
case Type::STRING:
data._string = new std::string(*other.data._string);
break;
// TODO: handle invalid values ...
}
}
return *this;
}
void Variant::clear()
{
if (type == Type::STRING)
{
delete data._string;
}
type = Type::NIL;
}
Variant::~Variant()
{
clear();
}
bool Variant::operator==(const Variant& other) const
{
if (type != other.type) return false;
switch (type)
{
case Type::NIL: return true;
case Type::BOOL: return data._bool == other.data._bool;
case Type::INT: return data._int == other.data._int;
case Type::FLOAT: return data._float == other.data._float;
case Type::STRING: return *data._string == *other.data._string;
default: return false;
}
}
bool Variant::is_zero() const
{
switch (type)
{
case NIL:
{
return true;
}
// Atomic types.
case BOOL:
{
return !(data._bool);
}
case INT:
{
return data._int == 0;
}
case FLOAT:
{
return data._float == 0;
}
case STRING:
{
return (*data._string).empty();
}
default:
{
}
}
return false;
}
// We consider all uninitialized or empty types to be false based on the type's
// zeroiness.
bool Variant::booleanize() const
{
return !is_zero();
}
Variant::operator bool() const
{
return booleanize();
}
Variant::operator int64_t() const
{
return _to_int<int64_t>();
}
Variant::operator int32_t() const
{
return _to_int<int32_t>();
}
Variant::operator int16_t() const
{
return _to_int<int16_t>();
}
Variant::operator int8_t() const
{
return _to_int<int8_t>();
}
Variant::operator uint64_t() const
{
return _to_int<uint64_t>();
}
Variant::operator uint32_t() const
{
return _to_int<uint32_t>();
}
Variant::operator uint16_t() const
{
return _to_int<uint16_t>();
}
Variant::operator uint8_t() const
{
return _to_int<uint8_t>();
}
Variant::operator char32_t() const
{
return operator uint32_t();
}
Variant::operator float() const
{
return _to_float<float>();
}
Variant::operator double() const
{
return _to_float<double>();
}
Variant::operator String() const
{
return stringify(0);
}
String Variant::stringify(int recursion_count) const
{
switch (type)
{
case NIL:
return "<null>";
case BOOL:
return data._bool ? "true" : "false";
case INT:
return std::to_string(data._int);
case FLOAT:
return std::to_string(data._float);
case STRING:
return *data._string;
default:
{
return "<" + get_type_name(type) + ">";
}
}
}
String Variant::get_type_name(Variant::Type p_type)
{
switch (p_type)
{
case NIL:
{
return "Nil";
}
case BOOL:
{
return "bool";
}
case INT:
{
return "int";
}
case FLOAT:
{
return "float";
}
case STRING:
{
return "String";
}
default:
{
}
}
return "";
}
std::ostream& operator<<(std::ostream& os, const Variant& variant)
{
switch (variant.get_type())
{
case Variant::NIL:
os << "null";
break;
case Variant::BOOL:
os << (variant.booleanize() ? "true" : "false");
break;
case Variant::INT:
os << static_cast<int64_t>(variant);
break;
case Variant::FLOAT:
os << static_cast<float>(variant);
break;
case Variant::STRING:
os << static_cast<std::string>(variant);
break;
default:
os << "<unknown>";
break;
}
return os;
}
int main()
{
Variant variant_n;
Variant variant_b = true;
Variant variant_i = 14;
Variant variant_f = 32.0f;
Variant variant_d = 67.3;
Variant variant_s = "hello world";
bool value_b = variant_b;
int value_i = variant_i;
float value_f = variant_f;
double value_d = variant_d;
String value_s = variant_s;
std::cout << "value b: " << value_b << std::endl;
std::cout << "value i: " << value_i << std::endl;
std::cout << "value f: " << value_f << std::endl;
std::cout << "value d: " << value_d << std::endl;
std::cout << "value s: " << value_s << std::endl;
std::cout << variant_f << std::endl;
assert(value_b == true);
assert(value_i == 14);
assert(value_f == 32.0f);
assert(value_d == 67.3);
assert(value_s == "hello world");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment