Reputation: 288
I want to create efficient and easy to use value type.
Base of the Value
is a boost::variant
(and std::variant
in the future), but I'm new in it.
And I have some questions:
boost::variant
? Maybe more efficient way exists?
class Value;
typedef std::string String;
typedef std::vector<char> BinData;
typedef String URL;
typedef unsigned long long UID;
TSW_STRONG_TYPEDEF(std::time_t, Time)
typedef std::vector<Value> ValueArray;
typedef std::vector<String> StringArray;
//typedef std::pair<String, Value> NameValue;
typedef std::list<Value> ValueList;
typedef std::list<String> StringList;
typedef std::map<String, String> StringStringMap;
typedef std::map<String, Value> NameValueMap;
struct monostate
{
monostate() = default;
};
constexpr bool operator<(monostate, monostate) noexcept { return false; }
constexpr bool operator>(monostate, monostate) noexcept { return false; }
constexpr bool operator<=(monostate, monostate) noexcept { return true; }
constexpr bool operator>=(monostate, monostate) noexcept { return true; }
constexpr bool operator==(monostate, monostate) noexcept { return true; }
constexpr bool operator!=(monostate, monostate) noexcept { return false; }
typedef monostate Null;
class Object
{
public:
Object() = delete;
Object(const Object &other) = default;
Object(Object &&other);
Object(const String &name);
Object(String &&name);
Object(const String &name, const NameValueMap &fields);
Object(String &&name, const NameValueMap &fields);
Object(const String &name, NameValueMap &&fields);
Object(String &&name, NameValueMap &&fields);
Object &operator=(const Object &other) = default;
Object &operator=(Object &&other);
public:
const String &get_name() const;
const NameValueMap &get_fields() const;
public:
bool operator<(const Object &other) const noexcept;
bool operator>(const Object &other) const noexcept;
bool operator<=(const Object &other) const noexcept;
bool operator>=(const Object &other) const noexcept;
bool operator==(const Object &other) const noexcept;
bool operator!=(const Object &other) const noexcept;
private:
String name_;
NameValueMap fields_;
};
enum class ValueType
{
Undefined, Null, Array, BinData, Boolean, DoubleNumber, Int64Number, String, Time, Object
};
// Types ordnung need to be same with ValueType ordnung.
/// Base for the Value class
typedef boost::variant<monostate, Null, ValueArray, BinData, bool, double, int64_t, String, Time, Object> ValueBase;
/**
* @brief The Value class, implements common framework value.
*
* This class is a container, which can store multiple values, including Values containers.
*
* @note
* Class based on a variant class. It may be either boost::variant or std::variant in C++17 and higher.
*/
class Value : public ValueBase
{
public:
using ValueBase::ValueBase;
Value() = default;
Value(const String::value_type *v) : ValueBase(String(v)) {}
public:
bool is_array() const { return static_cast<ValueType>(which()) == ValueType::Array; }
bool is_bool() const { return static_cast<ValueType>(which()) == ValueType::Boolean; }
bool is_bindata() const { return static_cast<ValueType>(which()) == ValueType::BinData; }
bool is_double() const { return static_cast<ValueType>(which()) == ValueType::DoubleNumber; }
bool is_int64() const { return static_cast<ValueType>(which()) == ValueType::Int64Number; }
bool is_null() const { return static_cast<ValueType>(which()) == ValueType::Null; }
bool is_object() const { return static_cast<ValueType>(which()) == ValueType::Object; }
bool is_string() const { return static_cast<ValueType>(which()) == ValueType::String; }
bool is_time() const { return static_cast<ValueType>(which()) == ValueType::Time; }
bool is_undefined() const { return static_cast<ValueType>(which()) == ValueType::Undefined; }
public:
bool as_bool() const { return as<bool>(); }
BinData &as_bindata() { return as<BinData>(); }
double as_double() const { return as<double>(); }
int64_t as_int64() const { return as<int64_t>(); }
Object &as_object() { return as<Object>(); }
String &as_string() { return as<String>(); }
Time &as_time() { return as<Time>(); }
ValueArray &as_array() { return as<ValueArray>(); }
public:
ValueType value_type() const { return static_cast<ValueType>(which()); }
public:
template <typename T>
const T& as() const { return boost::get<T>(*this); }
template <typename T>
T& as() { return boost::get<T>(*this); }
template <typename T>
const T& as(const T& default_value) const { return type() == typeid(T) ? boost::get<T>(*this) : default_value; }
template <typename T>
T& as(const T& default_value) { return type() == typeid(T) ? boost::get<T>(*this) : default_value; }
template <typename T> boost::optional<T> as_optional() { return boost::make_optional(type() == typeid(T), as<T>()); }
public:
bool operator==(const ValueBase &other) const { return ValueBase::operator==(other); }
bool operator<(const ValueBase &other) const { return ValueBase::operator<(other); }
bool operator>(const ValueBase &other) const { return !((*this) < other || (*this) == other); }
bool operator<=(const ValueBase &other) const { return ((*this) < other || (*this) == other); }
bool operator>=(const ValueBase &other) const { return !((*this) < other); }
bool operator!=(const ValueBase &other) const { return !((*this) == other); }
private:
// Force compile error, prevent Variant(bool) to be called
Value(void *) = delete;
};
Upvotes: 0
Views: 335
Reputation: 393613
Looks okay to me.
You can do without recursive variants IFF your standard library implementation allows instantiation of the container classes for incomplete types.
I'd note that since everything is tied to the base-class publicly, there is nothing about the implementation that can change without also changing the (binary) interface. Therefore I'd surely implement all the members inline so that the compiler will optimize them even without LTO.
It is not clear to me what to_X
members do (possibly just a<X>
but possibly something else depending on can_convert()
?). If it's just a wrap around as_<>
I'd rename them as_X()
etc.
You might also want to add optional<>
-like members like
template <typename T> T const& get_value_or(T const& default_value) const;
And possibly
template <typename T> optional<T> get() const;
// with boost optional you can prevent a copy²:
template <typename T> optional<T const&> get() const;
This enables code like:
if (auto& s = value.get<String>()) {
std::cout << "The string value is '" << *s << "'\n";
} else {
std::cout << "Value has no string value\n";
}
¹ this is not - yet - standard specified. You can always use Boost Container instead which promises this, as well as non-allocating construction
² just make sure you do not allow the operation on a rvalue, to remove a predictable class of errors, so e.g.
template <typename T> optional<T const&> get()&& = delete;
Upvotes: 2