libki/include/ki/pclass/Value.h

405 lines
10 KiB
C
Raw Normal View History

2018-10-25 10:39:16 +00:00
#pragma once
#include <utility>
#include <typeinfo>
2018-12-04 22:24:26 +00:00
#include <sstream>
2018-12-13 00:57:56 +00:00
#include <unordered_map>
2018-12-04 22:24:26 +00:00
#include "ki/util/exception.h"
2018-10-25 10:39:16 +00:00
namespace ki
{
namespace pclass
{
2018-12-04 22:24:26 +00:00
class Value;
class PropertyClass;
class IEnum;
2018-12-04 22:24:26 +00:00
namespace detail
{
template <typename SrcT, typename DestT>
struct value_caster;
2018-12-04 22:24:26 +00:00
/**
* A base class for Value casters.
* Provides a common interface for cast()
2018-12-04 22:24:26 +00:00
*/
struct value_caster_base
{
virtual ~value_caster_base() {}
/**
* @param[in] value The value being casted.
* @returns A new Value holding a casted value.
* @throws ki::cast_error If the cast was unsuccessful.
*/
virtual Value cast(const Value &value) const;
};
/**
* A base class for Value caster implementations.
* Provides a shortcut for throwing a cast_error.
*/
template <typename SrcT, typename DestT>
struct value_caster_impl : value_caster_base
{
2018-12-04 22:24:26 +00:00
protected:
static Value bad_cast();
};
/**
* A base class for Value deallocators.
* Provides a common interface for deallocate()
*/
struct value_deallocator_base
{
virtual ~value_deallocator_base() {}
2018-12-04 22:24:26 +00:00
/**
* Deallocate a Value-owned pointer.
* @param[in] ptr The pointer to deallocate.
2018-12-04 22:24:26 +00:00
*/
virtual void deallocate(void *ptr) const = 0;
2018-12-04 22:24:26 +00:00
};
/**
* TODO: Documentation
*/
template <typename T>
struct value_deallocator : value_deallocator_base
2018-12-04 22:24:26 +00:00
{
void deallocate(void *ptr) const override
{
// By default, now that we have the proper type, just delete it.
delete static_cast<T *>(ptr);
}
};
/**
* TODO: Documentation
*/
class ValueDeallocator
{
// Allow Value to call make<T>() and the default constructor
2018-12-04 22:24:26 +00:00
friend Value;
public:
ValueDeallocator(ValueDeallocator &&that) noexcept;
ValueDeallocator &operator=(ValueDeallocator &&that) noexcept;
~ValueDeallocator();
2018-12-04 22:24:26 +00:00
/**
* Deallocate a Value-owned pointer.
* @param[in] ptr The pointer to deallocate.
2018-12-04 22:24:26 +00:00
*/
void deallocate(void *ptr) const;
2018-12-04 22:24:26 +00:00
private:
value_deallocator_base *m_deallocator;
2018-12-04 22:24:26 +00:00
ValueDeallocator();
explicit ValueDeallocator(value_deallocator_base *deallocator);
2018-12-04 22:24:26 +00:00
template <typename T>
static ValueDeallocator make()
2018-12-04 22:24:26 +00:00
{
return ValueDeallocator(new value_deallocator<T>());
2018-12-04 22:24:26 +00:00
}
};
}
2018-10-25 10:39:16 +00:00
/**
* TODO: Documentation
*/
class ValueCaster
2018-10-25 10:39:16 +00:00
{
// Allow Value to call the default constructor and get<SrcT>()
friend Value;
2018-10-25 10:39:16 +00:00
public:
~ValueCaster();
/**
* Declare a run-time casting interface from SrcT to DestT.
* @tparam SrcT The cast source type.
* @tparam DestT the cast destination type.
*/
template <typename SrcT, typename DestT>
static void declare()
2018-10-25 10:39:16 +00:00
{
2018-12-11 01:19:30 +00:00
ValueCaster &caster = ValueCaster::get<SrcT>();
caster.add_caster<SrcT, DestT>();
}
/**
* @tparam SrcT The cast source type.
* @tparam DestT The cast destination type.
* @param[in] value The Value that is being casted to the destination type.
* @returns A Value with a reference to a DestT value/instance.
*/
template <typename SrcT, typename DestT>
static Value cast(const Value &value);
/**
* @tparam DestT The cast destination type.
* @param[in] src_type The std::type_info object of the source type.
* @param[in] value The Value that is being casted to the destination type.
* @returns A Value with a reference to a DestT value/instance.
*/
template <typename DestT>
static Value cast(const std::type_info &src_type, const Value &value);
/**
* @tparam DestT The cast destination type.
* @param[in] value The Value that is being casted to the destination type.
* @returns A Value with a reference to a DestT value/instance.
*/
template <typename DestT>
Value cast_value(const Value &value) const;
private:
/**
* A static lookup used to find appropriate casters at runtime.
* Contains SrcT -> Caster elements.
*/
2018-12-13 00:57:56 +00:00
static std::unordered_map<std::size_t, ValueCaster *> s_caster_lookup;
const std::type_info *m_src_type;
2018-12-13 00:57:56 +00:00
std::unordered_map<std::size_t, detail::value_caster_base *> m_casts;
ValueCaster();
explicit ValueCaster(const std::type_info &src_type);
/**
2018-12-13 00:57:56 +00:00
* @tparam SrcT The cast source type.
* @returns The ValueCaster instance responsible for casting values of type SrcT.
*/
template <typename SrcT>
static ValueCaster &get()
{
const auto &src_type = typeid(SrcT);
const auto src_type_hash = src_type.hash_code();
if (s_caster_lookup.find(src_type_hash) == s_caster_lookup.end())
s_caster_lookup[src_type_hash] = new ValueCaster(src_type);
return *s_caster_lookup[src_type_hash];
2018-10-25 10:39:16 +00:00
}
template <typename SrcT, typename DestT>
void add_caster()
{
const auto dest_type_hash = typeid(DestT).hash_code();
m_casts[dest_type_hash] = new detail::value_caster<SrcT, DestT>();
}
};
/**
* A wrapper around a void pointer that ensures type safety.
*/
class Value
{
public:
Value(Value &&that) noexcept;
Value &operator=(Value &&that) noexcept;
~Value();
2018-10-25 10:39:16 +00:00
/**
* @returns Whether or the not the value is holding a reference or a value.
*/
bool is_reference() const
{
// If the pointer isn't owned, then it isn't this Value's responsibility
// to clean it up, so we say it's referencing something.
return !m_ptr_is_owned;
}
/**
* @returns Whether or not the value being held is of type T.
2018-12-04 22:24:26 +00:00
*/
2018-10-25 10:39:16 +00:00
template <typename T>
bool is() const
{
// Do the type hashes match?
return m_type_hash == typeid(T).hash_code();
}
/**
* @tparam T
* @returns A new Value instance that owns it's value.
*/
template <typename T>
Value dereference() const
{
// Do we need to attempt casting?
if (!is<T>())
return m_caster->cast_value<T>(*this);
return Value::make_value<T>(*static_cast<T *>(m_value_ptr));
}
2018-10-25 10:39:16 +00:00
/**
* @tparam T The expected type.
* @returns A reference to the value being held.
* @throws ki::runtime_error The expected type and the type of the value being held are not the same.
2018-12-04 22:24:26 +00:00
*/
2018-10-25 10:39:16 +00:00
template <typename T>
const T &get() const
{
// Make sure they requested the correct type
2018-10-25 10:39:16 +00:00
if (!is<T>())
throw runtime_error("Invalid call to Value::get<T>.");
// Return a reference to the value being held
return *static_cast<T *>(m_value_ptr);
2018-10-25 10:39:16 +00:00
}
/**
* @tparam T The expected type.
* @returns A reference to the value being held.
* @throws ki::runtime_error If the expected type and the type of the value being held are not the same.
2018-12-04 22:24:26 +00:00
*/
2018-10-25 10:39:16 +00:00
template <typename T>
T &get()
{
// Make sure they requested the correct type
2018-10-25 10:39:16 +00:00
if (!is<T>())
throw runtime_error("Invalid call to Value::get<T>.");
// Return a reference to the value being held
return *static_cast<T *>(m_value_ptr);
2018-10-25 10:39:16 +00:00
}
/**
* @tparam T The expected type.
* @returns A pointer to the value being held (that the caller takes ownership of).
* @throws ki::runtime_error If the Value is a reference.
* @throws ki::runtime_error If the expected type and the type of the value being held are not the same.
*/
template <typename T>
T *take()
{
// Make sure this Value is not a reference
if (is_reference())
throw runtime_error("Cannot take ownership from a reference Value.");
// Make sure they requested the correct type
if (!is<T>())
throw runtime_error("Invalid call to Value::get<T>.");
// Give up the pointer (this Value becomes a reference)
m_ptr_is_owned = false;
return static_cast<T *>(m_value_ptr);
}
/**
* @tparam T The type of value to hold.
* @param[in] value The initial value.
* @returns A new Value instance that owns it's value.
*/
template <typename T>
static Value make_value(T value)
{
auto *ptr = static_cast<void *>(new T(value));
auto val = Value(ptr, true);
val.construct<T>();
return val;
}
/**
* @tparam T The type of value to hold.
* @param[in] value The initial value.
* @returns A new Value instance that refers to a value it doesn't own.
*/
template <typename T>
static Value make_reference(T &value)
{
auto *ptr = static_cast<void *>(&value);
auto val = Value(ptr);
val.construct<T>();
return val;
}
/**
* @tparam T The type of value to hold.
* @param[in] value The initial value.
* @returns A new Value instance that refers to a value it doesn't own.
*/
template <typename T>
static Value make_reference(const T &value)
{
auto *ptr = const_cast<void *>(static_cast<const void *>(&value));
auto val = Value(ptr);
val.construct<T>();
return val;
}
2018-10-25 10:39:16 +00:00
private:
void *m_value_ptr;
bool m_ptr_is_owned;
2018-10-25 10:39:16 +00:00
std::size_t m_type_hash;
ValueCaster *m_caster;
detail::ValueDeallocator m_deallocator;
explicit Value(void *value_ptr, bool owned = false);
/**
* Set values that depend on knowing T.
* @tparam T The type that is being held.
*/
template <typename T>
void construct()
2018-12-04 22:24:26 +00:00
{
m_type_hash = typeid(T).hash_code();
m_caster = &ValueCaster::get<T>();
m_deallocator = detail::ValueDeallocator::make<T>();
2018-12-04 22:24:26 +00:00
}
};
2018-12-04 22:24:26 +00:00
template <typename DestT>
Value ValueCaster::cast(const std::type_info &src_type, const Value& value)
{
const auto it = s_caster_lookup.find(src_type.hash_code());
if (it != s_caster_lookup.end())
2018-12-11 01:19:30 +00:00
return it->second->cast_value<DestT>(value);
throw cast_error(src_type, typeid(DestT));
}
template <typename SrcT, typename DestT>
Value ValueCaster::cast(const Value& value)
{
ValueCaster::declare<SrcT, DestT>();
2018-12-11 01:19:30 +00:00
ValueCaster &caster = ValueCaster::get<SrcT>();
return caster.cast_value<DestT>(value);
}
template <typename DestT>
Value ValueCaster::cast_value(const Value& value) const
{
const auto it = m_casts.find(typeid(DestT).hash_code());
if (it != m_casts.end())
return it->second->cast(value);
throw cast_error(*m_src_type, typeid(DestT));
}
namespace detail
{
template <typename SrcT, typename DestT>
Value value_caster_impl<SrcT, DestT>::bad_cast()
2018-12-04 22:24:26 +00:00
{
throw cast_error(typeid(SrcT), typeid(DestT));
2018-12-04 22:24:26 +00:00
}
/**
* TODO: Documentation
*/
template <typename SrcT, typename DestT>
struct value_caster : value_caster_impl<SrcT, DestT>
2018-12-04 22:24:26 +00:00
{
Value cast(const Value &value) const override
2018-12-04 22:24:26 +00:00
{
// By default, just attempt to static_cast from SrcT to DestT.
2018-12-13 00:57:56 +00:00
return Value::make_value<DestT>(
static_cast<DestT>(value.get<SrcT>())
);
2018-12-04 22:24:26 +00:00
}
};
}
}
2018-10-25 10:39:16 +00:00
}