-
-
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; | |
} |
Is there a specific reason not to use std::aligned_union? (http://en.cppreference.com/w/cpp/types/aligned_union)
Worked quite well for me and compiles also in VS2013 (which does not support alignof).
Thanks for the great code and the tutorial on your blog! 😃
FWIW, http://en.cppreference.com/w/cpp/types/type_info/hash_code says
the same value may be returned for different types
It is pretty trivial to change size_t type_id
to const std::type_info &type_id
in the code above and then rely on std::type_info operator==
to test for type equality.
I would rip out the typeinfo altogether and just use sizeof... to give each type a unique number.
BTW.. I learned a lot from this. Thank you for the post.
Could you add a license to this code? (Ideally, something permissive, like MIT or BSD.) That way we could use it verbatim in our own projects. Thanks.
@tuxication It seems that is() and valid() are not throwing an error or warning when compiling however, to make them function correctly you will need to change the function prototype to return a bool.
bool is() {...}
bool valid() {...}
I had to make this change for it to work correctly in my application I am using it in.
Calling std::swap
on the aligned_storage
causes undefined behavior with non-trivially copyable types. With libstdc++ 5.3 I was getting an "invalid free" error with std::string
upon its destruction after swapping.
https://gist.github.com/ricejasonf/e1011544d14bf2978f1e58c95e6b8902
To restrict get/set to only valid datatypes:
template <typename...> struct is_one_of {
static constexpr bool value = false;
};
template <typename T, typename S, typename... Ts> struct is_one_of<T, S, Ts...> {
static constexpr bool value = std::is_same<T, S>::value || is_one_of<T, Ts...>::value;
};
And then in the class definition:
template <typename T, typename... Args, typename = typename std::enable_if<is_one_of<T, Ts...>::value, void>::type>
T& get() { ... }
template <typename T, typename... Args, typename = typename std::enable_if<is_one_of<T, Ts...>::value, void>::type>
void set(Args &&... args) { ... }
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.
Hi, I'm not a C++11 expert but I was looking to implement a simple variant class and found yours. One quick comment, is it normal that the is() and valid() member function from your variant class are void and both try to return a bool ?