Have been using std::variant for the first time.
Accidentally stumbled on a pattern that seems resonable but wanted to get a second opinion.
I have a class that can contain value (its for another language simulation). To represent this in C++ I though the value can use a variant and thus I can retain type safety. But now I need to get the data out and using std::variant::index works but is less readable in the code than I want. So I initially added a type object to the class that I kept in sync with the variant:
Think:
// Note: This is not the code for review.
// This is to give context.
class Object {/*Stuff*/}
enum class DataType = {Integer, Double, String, Bool, Object};
using DataValue = std::vaiant<int, double, std::string, bool, Object>;
class Value
{
DataType typeInfo;
DataValue value;
public:
// Removed constructors for clarity.
DataType type() const {return typeInfo;}
template<typename T>
T const& get() const {return std::get<T>(value);}
};
This way I to perform actions on the value I can use a switch:
void doAction(Value& v)
{
switch (v.type()) {
case DataType::Integer: doAction(v.get<int>());break;
case DataType::Double: doAction(v.get<double>());break;
case DataType::String: doAction(v.get<std::string>());break;
case DataType::Bool: doAction(v.get<bool>());break;
case DataType::Object: doAction(v.get<Object>());break;
}
}
So two things stand out to me.
- In the
get<X>()theXdoes not match theDataType::Xapart formObject. - I have stored an extra type object (I am sure that variant is already got).
To the code to review:
I changed two things. I don't store the extra typeInfo object (I calculate it from value) and I add some extra type aliases to make the code cleaner and easier to read:
This is what my code looks like now:
namespace ThorsAnvil::Anvil::Ice
{
class Object {/*Stuff*/}
enum class DataType = {Integer, Double, String, Bool, Object};
using Integer = int;
using Double = double;
using String = std::string;
using Bool = std::bool;
using DataValue = std::vaiant<Integer, Double, String, Bool, Object>;
class Value
{
DataValue value;
public:
// Removed constructors for clarity.
DataType type() const {return static_cast<DataType>(value.index());}
template<typename T>
T const& get() const {return std::get<T>(value);}
Value toInteger(Value& v) const
{
switch (v.type()) {
case DataType::Integer: return Value{get<Integer>()};break;
case DataType::Double: return Value{static_cast<int>(get<Double>())};break;
case DataType::String: return Value{std::atoi(get<String>().c_str())};break;
case DataType::Bool: return Value{get<Bool>()? 1 : 0};break;
case DataType::Object: return Value{get<Object>().toNumber()};break;
}
}
};
}