Note: I'm aware of boost::variant
, but I am curious about the design principles. This question mostly for self-education.
Original post
At my current job I found an old variant class implementation. It's implemented with a union
and can only support a handful of datatypes. I've been thinking about how one should go about designing an improved version. After some tinkering I ended up with something that seems to work. However I'd like to know your opinion about it. Here it is:
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>
#include <typeinfo>
#include <boost/shared_ptr.hpp>
class Variant
{
public:
Variant() { }
template<class T>
Variant(T inValue) :
mImpl(new VariantImpl<T>(inValue)),
mClassName(typeid(T).name())
{
}
template<class T>
T getValue() const
{
if (typeid(T).name() != mClassName)
{
throw std::logic_error("Non-matching types!");
}
return dynamic_cast<VariantImpl<T>*>(mImpl.get())->getValue();
}
template<class T>
void setValue(T inValue)
{
mImpl.reset(new VariantImpl<T>(inValue));
mClassName = typeid(T).name();
}
private:
struct AbstractVariantImpl
{
virtual ~AbstractVariantImpl() {}
};
template<class T>
struct VariantImpl : public AbstractVariantImpl
{
VariantImpl(T inValue) : mValue(inValue) { }
~VariantImpl() {}
T getValue() const { return mValue; }
T mValue;
};
boost::shared_ptr<AbstractVariantImpl> mImpl;
std::string mClassName;
};
int main()
{
// Store int
Variant v(10);
int a = 0;
a = v.getValue<int>();
std::cout << "a = " << a << std::endl;
// Store float
v.setValue<float>(12.34);
float d = v.getValue<float>();
std::cout << "d = " << d << std::endl;
// Store map<string, string>
typedef std::map<std::string, std::string> Mapping;
Mapping m;
m["one"] = "uno";
m["two"] = "due";
m["three"] = "tre";
v.setValue<Mapping>(m);
Mapping m2 = v.getValue<Mapping>();
std::cout << "m2["one"] = " << m2["one"] << std::endl;
return 0;
}
Output is correct:
a = 10
d = 12.34
m2["one"] = uno
My SO questions are:
- Is this implementation correct?
- Will the dynamic cast in
getValue()
work as expected (I'm not certain) - Should I return T as a const reference instead? Or can I count on return-value-optimization to kick in?
- Any other problems or suggestions?
Update
Thanks to @templatetypedef for his suggestions. This updated version only uses dynamic_cast
to check if the types match. Type mismatches caused by differences in constness are now avoided thanks to the TypeWrapper classes (which I have shamelessly stolen from the Poco C++ project).
So this is the current version. It's likely to contain a few errors though, as I'm not familiar with the idea of modifying const/ref on template templates. I'll have a fresh look tomorrow.
template <typename T>
struct TypeWrapper
{
typedef T TYPE;
typedef const T CONSTTYPE;
typedef T& REFTYPE;
typedef const T& CONSTREFTYPE;
};
template <typename T>
struct TypeWrapper<const T>
{
typedef T TYPE;
typedef const T CONSTTYPE;
typedef T& REFTYPE;
typedef const T& CONSTREFTYPE;
};
template <typename T>
struct TypeWrapper<const T&>
{
typedef T TYPE;
typedef const T CONSTTYPE;
typedef T& REFTYPE;
typedef const T& CONSTREFTYPE;
};
template <typename T>
struct TypeWrapper<T&>
{
typedef T TYPE;
typedef const T CONSTTYPE;
typedef T& REFTYPE;
typedef const T& CONSTREFTYPE;
};
class Variant
{
public:
Variant() { }
template<class T>
Variant(T inValue) :
mImpl(new VariantImpl<typename TypeWrapper<T>::TYPE>(inValue))
{
}
template<class T>
typename TypeWrapper<T>::REFTYPE getValue()
{
return dynamic_cast<VariantImpl<typename TypeWrapper<T>::TYPE>&>(*mImpl.get()).mValue;
}
template<class T>
typename TypeWrapper<T>::CONSTREFTYPE getValue() const
{
return dynamic_cast<VariantImpl<typename TypeWrapper<T>::TYPE>&>(*mImpl.get()).mValue;
}
template<class T>
void setValue(typename TypeWrapper<T>::CONSTREFTYPE inValue)
{
mImpl.reset(new VariantImpl<typename TypeWrapper<T>::TYPE>(inValue));
}
private:
struct AbstractVariantImpl
{
virtual ~AbstractVariantImpl() {}
};
template<class T>
struct VariantImpl : public AbstractVariantImpl
{
VariantImpl(T inValue) : mValue(inValue) { }
~VariantImpl() {}
T mValue;
};
boost::shared_ptr<AbstractVariantImpl> mImpl;
};
See Question&Answers more detail:os