My goal is to learn a bit more about generic programming in C++. So, one of the trickiest things I've heard you can do is creating a Variant class. This took me a while and I needed to read and study a few implementations:
#ifndef VARIANT_HPP_INCLUDED
#define VARIANT_HPP_INCLUDED
#include <cstddef>
#include <typeindex>
#include <type_traits>
#include <iostream>
namespace spaceengine
{
namespace utils
{
template<typename... Ts>
struct VariantHelper;
template<typename F, typename... Ts>
struct VariantHelper<F, Ts...>
{
inline static void Destroy(const std::type_index typeIndex, void* data)
{
if(typeIndex == std::type_index(typeid(F)))
reinterpret_cast<F*>(data)->~F();
else
VariantHelper<Ts...>::Destroy(typeIndex,data);
}
inline static void Move(const std::type_index oldTypeIndex, void* oldV, void* newV)
{
if(oldTypeIndex == std::type_index(typeid(F)))
new (newV) F(std::move(*reinterpret_cast<F*>(oldV)));
else
VariantHelper<Ts...>::Move(oldTypeIndex,oldV,newV);
}
inline static void Copy(const std::type_index oldTypeIndex, const void* oldV, void* newV)
{
if(oldTypeIndex == std::type_index(typeid(F)))
new (newV) F(*reinterpret_cast<const F*>(oldV));
else
VariantHelper<Ts...>::Copy(oldTypeIndex,oldV,newV);
}
};
template<>
struct VariantHelper<>
{
inline static void Destroy(const std::type_index typeIndex, void* data){}
inline static void Move(const std::type_index oldTypeIndex, void* oldV, void* newV) {}
inline static void Copy(const std::type_index oldTypeIndex, const void* oldV, void* newV) {}
};
template<typename... Ts>
class Variant
{
public:
Variant() : m_typeIndex(InvalidType()){}
Variant(const Variant<Ts...>& old) : m_typeIndex(old.m_typeIndex)
{
HelperType::Copy(old.m_typeIndex, &old.m_data, &m_data);
}
Variant(Variant<Ts...>&& old) : m_typeIndex(old.m_typeIndex)
{
HelperType::Move(old.m_typeIndex, &old.m_data, &m_data);
}
Variant<Ts...>& operator=(Variant<Ts...> old)
{
m_typeIndex = old.m_typeIndex;
Variant<Ts...> temp(*this);
HelperType::Copy(old.m_typeIndex,&old.m_data,&m_data);
HelperType::Copy(temp.m_typeIndex,&temp.m_data,&old.m_data);
return *this;
}
template<typename T>
bool Is() const
{
return (m_typeIndex == std::type_index(typeid(T)));
}
bool IsValid() const
{
return (m_typeIndex != std::type_index(typeid(InvalidType())));
}
std::type_index GetTypeIndex() const { return m_typeIndex; }
template<typename T, typename... Args>
void Set(Args&&... args)
{
HelperType::Destroy(m_typeIndex,&m_data);
new (&m_data) T(std::forward<Args>(args)...);
m_typeIndex = std::type_index(typeid(T));
}
template<typename T>
const T& Get() const
{
if(m_typeIndex == std::type_index(typeid(T)))
return *reinterpret_cast<const T*>(&m_data);
else
throw std::bad_cast();
}
template<typename T>
T& Get()
{
if(m_typeIndex == std::type_index(typeid(T)))
return *reinterpret_cast<T*>(&m_data);
else
throw std::bad_cast();
}
~Variant()
{
HelperType::Destroy(m_typeIndex,&m_data);
}
private:
using DataType = typename std::aligned_union<1,Ts...>::type;
using HelperType = VariantHelper<Ts...>;
static inline std::type_index InvalidType()
{
return std::type_index(typeid(void));
}
std::type_index m_typeIndex;
DataType m_data;
};
}
}
#endif // VARIANT_HPP_INCLUDED
What I'm really looking for is:
- Is there anything that's usually required by a
Variant
that this class doesn't provide? - Have I missed anything else that's obvious?
- Does my use of placement new mean that my
Variant
class is stack allocated, or have I misunderstood what placement new does?
One of the things that irks me is that the boost::variant
class is very large; and it seems like my much smaller code does 100% of what I need. But I can't decipher boost::variant
well enough to figure out what it does that mine doesn't do.