Create a gist now

Instantly share code, notes, and snippets.

@tibordp /variant.cc
Last active Jul 10, 2018

Embed
What would you like to do?
A simple variant type implementation in C++
#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;
}
@tuxication

This comment has been minimized.

Show comment
Hide comment
@tuxication

tuxication Oct 9, 2014

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 ?

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 ?

@Wumpf

This comment has been minimized.

Show comment
Hide comment
@Wumpf

Wumpf Nov 2, 2014

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! 😃

Wumpf commented Nov 2, 2014

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! 😃

@wez

This comment has been minimized.

Show comment
Hide comment
@wez

wez Mar 8, 2015

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.

wez commented Mar 8, 2015

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.

@ricejasonf

This comment has been minimized.

Show comment
Hide comment
@ricejasonf

ricejasonf Aug 6, 2015

I would rip out the typeinfo altogether and just use sizeof... to give each type a unique number.

I would rip out the typeinfo altogether and just use sizeof... to give each type a unique number.

@ricejasonf

This comment has been minimized.

Show comment
Hide comment
@ricejasonf

ricejasonf Aug 6, 2015

BTW.. I learned a lot from this. Thank you for the post.

BTW.. I learned a lot from this. Thank you for the post.

@mrzv

This comment has been minimized.

Show comment
Hide comment
@mrzv

mrzv Sep 20, 2015

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.

mrzv commented Sep 20, 2015

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.

@tazdij

This comment has been minimized.

Show comment
Hide comment
@tazdij

tazdij Dec 29, 2015

@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.

tazdij commented Dec 29, 2015

@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.

@ricejasonf

This comment has been minimized.

Show comment
Hide comment
@ricejasonf

ricejasonf Jul 21, 2016

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

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

@stephan-kempkes

This comment has been minimized.

Show comment
Hide comment
@stephan-kempkes

stephan-kempkes Apr 21, 2017

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) { ... }

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) { ... }
@S6066

This comment has been minimized.

Show comment
Hide comment
@S6066

S6066 Dec 31, 2017

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

S6066 commented Dec 31, 2017

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment