Last active
April 2, 2025 12:38
-
-
Save nclettiere/3177b02f2a5b35745c8d35c8fb6750cb to your computer and use it in GitHub Desktop.
Tiny portable C++ Variant class based on the Godot Engine Variant class
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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