Here is a simple variant type I have made. For simplicity, it only can handle two different values. I am planning on using this to create a variadic variant template, using inheritance and recursion.
The requirements for my variant type:
- It must have performance similar to
std::variant
- It must not dynamically allocate memory
- There must be no undefined behavior
- It should check as much as possible at compile-time, and throw an exception if access is attempted to the wrong type
- It must support nontrivial types
Here is the source:
#include <iostream>
#include <cstddef>
#include <typeindex>
#include <optional>
#include <algorithm>
#include <stdexcept>
#include <functional>
/** A variant (sum type) that can contain one of two different types at any one time**/
template<typename T1, typename T2>
class Either {
using Bigest = std::conditional_t<sizeof(T1) <= sizeof(T2), T1, T2>;
alignas(sizeof(Bigest)) std::byte storage[sizeof(Bigest)];
std::optional<std::type_index> conatinedType;
std::function<void(std::byte*)> destructor;
public:
Either() : conatinedType(std::nullopt) {}
template<typename T>
Either(const T& value) : conatinedType(typeid(T)) {
static_assert(std::is_same<T1,T>::value || std::is_same<T2,T>::value, "Either cannot contain type T");
const auto src = reinterpret_cast<const std::byte*>(&value);
for(size_t i = 0; i < sizeof(T); i++) {
storage[i] = src[i];
}
destructor = [](std::byte* data){ (*reinterpret_cast<T*>(data)).~T(); };
}
class BadVariantAccess : public std::exception {};
template<typename T>
const T& getAs() const {
static_assert(std::is_same<T1,T>::value || std::is_same<T2,T>::value, "Either cannot contain type T");
if(conatinedType == typeid(T)) {
const auto ptr = reinterpret_cast<const T*>(&storage);
return *ptr;
}
else throw BadVariantAccess();
};
};
And a test:
struct MyInt {
int val;
MyInt(const int& val) : val(val) {
std::cout << "making int!" << std::endl;
}
~MyInt() {
std::cout << "destructing int!" << std::endl;
}
};
struct MyDouble {
double val;
MyDouble(const double& val) : val(val) {
std::cout << "making double!" << std::endl;
}
~MyDouble() {
std::cout << "destructing double!" << std::endl;
}
};
int main()
{
Either<MyInt, MyDouble> e = MyDouble(5.0987);
try{
std::cout << e.getAs<MyDouble>().val << std::endl;
}
catch(...) {
std::cerr << "Bad Variant Access" << std::endl;
}
return 0;
}
conatinedType
->containedType
,Bigest
->Biggest
. \$\endgroup\$