-
-
Save tibordp/6909880 to your computer and use it in GitHub Desktop.
#include <iostream> | |
#include <utility> | |
#include <typeinfo> | |
#include <type_traits> | |
#include <string> | |
template <size_t arg1, size_t ... others> | |
struct static_max; | |
template <size_t arg> | |
struct static_max<arg> | |
{ | |
static const size_t value = arg; | |
}; | |
template <size_t arg1, size_t arg2, size_t ... others> | |
struct static_max<arg1, arg2, others...> | |
{ | |
static const size_t value = arg1 >= arg2 ? static_max<arg1, others...>::value : | |
static_max<arg2, others...>::value; | |
}; | |
template<typename... Ts> | |
struct variant_helper; | |
template<typename F, typename... Ts> | |
struct variant_helper<F, Ts...> { | |
inline static void destroy(size_t id, void * data) | |
{ | |
if (id == typeid(F).hash_code()) | |
reinterpret_cast<F*>(data)->~F(); | |
else | |
variant_helper<Ts...>::destroy(id, data); | |
} | |
inline static void move(size_t old_t, void * old_v, void * new_v) | |
{ | |
if (old_t == typeid(F).hash_code()) | |
new (new_v) F(std::move(*reinterpret_cast<F*>(old_v))); | |
else | |
variant_helper<Ts...>::move(old_t, old_v, new_v); | |
} | |
inline static void copy(size_t old_t, const void * old_v, void * new_v) | |
{ | |
if (old_t == typeid(F).hash_code()) | |
new (new_v) F(*reinterpret_cast<const F*>(old_v)); | |
else | |
variant_helper<Ts...>::copy(old_t, old_v, new_v); | |
} | |
}; | |
template<> struct variant_helper<> { | |
inline static void destroy(size_t id, void * data) { } | |
inline static void move(size_t old_t, void * old_v, void * new_v) { } | |
inline static void copy(size_t old_t, const void * old_v, void * new_v) { } | |
}; | |
template<typename... Ts> | |
struct variant { | |
private: | |
static const size_t data_size = static_max<sizeof(Ts)...>::value; | |
static const size_t data_align = static_max<alignof(Ts)...>::value; | |
using data_t = typename std::aligned_storage<data_size, data_align>::type; | |
using helper_t = variant_helper<Ts...>; | |
static inline size_t invalid_type() { | |
return typeid(void).hash_code(); | |
} | |
size_t type_id; | |
data_t data; | |
public: | |
variant() : type_id(invalid_type()) { } | |
variant(const variant<Ts...>& old) : type_id(old.type_id) | |
{ | |
helper_t::copy(old.type_id, &old.data, &data); | |
} | |
variant(variant<Ts...>&& old) : type_id(old.type_id) | |
{ | |
helper_t::move(old.type_id, &old.data, &data); | |
} | |
// Serves as both the move and the copy asignment operator. | |
variant<Ts...>& operator= (variant<Ts...> old) | |
{ | |
std::swap(type_id, old.type_id); | |
std::swap(data, old.data); | |
return *this; | |
} | |
template<typename T> | |
void is() { | |
return (type_id == typeid(T).hash_code()); | |
} | |
void valid() { | |
return (type_id != invalid_type()); | |
} | |
template<typename T, typename... Args> | |
void set(Args&&... args) | |
{ | |
// First we destroy the current contents | |
helper_t::destroy(type_id, &data); | |
new (&data) T(std::forward<Args>(args)...); | |
type_id = typeid(T).hash_code(); | |
} | |
template<typename T> | |
T& get() | |
{ | |
// It is a dynamic_cast-like behaviour | |
if (type_id == typeid(T).hash_code()) | |
return *reinterpret_cast<T*>(&data); | |
else | |
throw std::bad_cast(); | |
} | |
~variant() { | |
helper_t::destroy(type_id, &data); | |
} | |
}; | |
struct test{ | |
int * holder; | |
test() { | |
std::cout << "test()" << std::endl; | |
holder = new int(); | |
} | |
test(test&& old) : holder(nullptr) { | |
std::cout << "test(test&&)" << std::endl; | |
std::swap(holder,old.holder); | |
} | |
test(const test& old) { | |
std::cout << "test(const test&)" << std::endl; | |
holder = new int(*old.holder); | |
} | |
~test() | |
{ | |
std::cout << "~test()" << std::endl; | |
delete holder; | |
} | |
}; | |
int main() { | |
using my_var = variant<std::string, test>; | |
my_var d; | |
d.set<std::string>("First string"); | |
std::cout << d.get<std::string>() << std::endl; | |
d.set<test>(); | |
*d.get<test>().holder = 42; | |
my_var e(std::move(d)); | |
std::cout << *e.get<test>().holder << std::endl; | |
*e.get<test>().holder = 43; | |
d = e; | |
std::cout << *d.get<test>().holder << std::endl; | |
} |
If you use C++11 but not C++17, here is a non RTTI variant (made from tibordp's one): https://gist.github.com/S6066/f726a37b2b703efea7ee27103e5bec89
I created a variant that doesn't depend on the standard library, and uses type level integer indices to access elements of the variant instead of type_id and hash_code. It is available here:
https://gist.github.com/calebh/fd00632d9c616d4b0c14e7c2865f3085
Thanks for your codes, it really helps!
I just found that hash_code is pretty time-consuming and I will try calebh's solution to optimize the performance.
I know it's an older thread but I've made a C++11 compatible version loosely based on @tibordp and @calebh versions as well as added functionality to:
visit
variant, which will call visitor's appropriateoperator()
overload according to the type it happends to be storing at the time- compile-time check for
set<T>()
type to match at least one ofvariant<Ts...>
types from a pack
I however wouldn't mind some help with making type_id()
generation purely in compile time. I have a hunch it can be done, I just cant figure it out for the life of me :(
Hi. Just curious why you don't use union
?
Hi. Just curious why you don't use
union
?
Unions don't support move/copy semantics which means that you can't have a union with a shared_ptr inside. Since the C/C++ runtime doesn't know which element of the union it holds, it cannot know if an element uses a copy constructor.
To restrict get/set to only valid datatypes:
And then in the class definition: