My question may look obvious and even incorrect, so I'll describe my problem from the very beginning. As it often happens, I may be misunderstanding the concept itself, therefore trying to do something which should be done in a totally different way.
Subj
I've been challenged to write a simple custom version of std::variant
. It's key features (when implementing one) are:
- Works like a type-dafe union
- Does not allocate extra memory dynamically
- Is properly aligned, according to the contained types
- There exist
get<T>
&get<index>
functions (in my case), which can obatinvariant
's data by type & index (that's where trouble began)
Problem
To achieve all the copy & move semantics the stored type is required to be available, so that if it is not POD, it's constructors and other stuff would be called. Also seems that get<index>()
relies upon the same ability to get that type. Would be fine... but it's obtained only in constructor, which means runtime.
Below is my small draft, it stores values and supports get<T>
:
/* Here goes template magic, don't read much of it,
* should be working fine, just showing relevant part of it
************************************************************/
inline constexpr std::size_t variant_npos = -1;
template<typename _Tp, typename... _Types>
struct __index_of : std::integral_constant<size_t, 0> {};
template<typename _Tp, typename... _Types>
struct __index_of_v
{
static constexpr size_t value = __index_of<_Tp, _Types...>::value;
};
template<typename _Tp, typename _First, typename... _Rest>
struct __index_of<_Tp, _First, _Rest...> :
std::integral_constant<size_t, std::is_same<_Tp, _First>::value ? 0 : __index_of_v<_Tp, _Rest...>::value + 1> {};
template<typename _Tp, typename... _Types>
struct __variant_index_of // !! This can get an index of type in variant's pack. Works ok.
{
static constexpr size_t value = __index_of_v<_Tp, _Types...>::value == sizeof...(_Types) ? 0 : __index_of_v<_Tp, _Types...>::value;
};
//---------------------- Here goes the class --------------------------------
template<typename... Types>
class variant
{
const __type_index<Types...> __npos = static_cast<__type_index<Types...>>(variant_npos); // Never mind
public:
variant()
: _index(__npos) // "No type" index
{ }
variant(const variant<Types...>& other)
: _data(other._data)
{ }
variant(variant<Types...>&& other)
: _data(other._data)
{
other._index = variant_npos;
}
/* Constructors contain type check, because
* supporting implicit conversions is good, these
* checks don't influence the current question
************************************************/
template<class U, typename std::enable_if<__variant_index_of<std::decay_t<U>, Types...>::value != 0 >::type* = nullptr>
variant(U&& value)
: _index(__variant_index_of<U, Types...>::value)
{
new (getPtr()) U{std::move(value)};
}
template<class U, typename std::enable_if<__checker<0, U, Types...>::is_conv
&& __variant_index_of<std::decay_t<U>, Types...>::value == 0 >::type* = nullptr>
variant(U&& value)
: _index(__checker<0, U, Types...>::number_of_class)
{
using Datatype = typename __checker<0, U, Types...>::TargetClass;
new (getPtr()) Datatype{std::move(value)};
}
variant<Types...>& operator=(const variant<Types...>& other)
{
// TODO: should be improved, as well as move should be implemented
if (this != &other)
{
_data = other._data;
_index = other._index;
}
return *this;
}
std::size_t index() const
{ return _index; }
bool empty() const
{ return _index == __npos; }
private:
void* getPtr() const
{ return const_cast<void*>(static_cast<const void*>(std::addressof(_data))); }
void* getPtr()
{ return static_cast<void*>(std::addressof(_data)); }
private:
std::aligned_union_t<1, Types...> _data;
// Taken from GCC implementation, a sophisticated index type for alignment
// Assume it is an unsigned number
__type_index<Types...> _index;
static_assert(sizeof...(Types) > 0, "There must be at least one type alternative");
template<typename T, typename... OtherTypes>
friend T& get(variant<OtherTypes...>& v);
template<typename T, typename... OtherTypes>
friend const T& get(const variant<OtherTypes...>& v);
template<typename T, typename... OtherTypes>
friend T* get(variant<OtherTypes...>* v);
template<typename T, typename... OtherTypes>
friend const T* get(const variant<OtherTypes...>* v);
template<typename T, typename... OtherTypes>
friend T&& get(const variant<OtherTypes...>&& v);
};
//----------------- The latter 3 get functions are almost similar ------
template<typename T, typename... Types>
T& get(variant<Types...>& v)
{
return const_cast<T&>(get<T>(const_cast<const variant<Types...>&>(v)));
}
template<typename T, typename... Types>
const T& get(const variant<Types...>& v)
{
if ((v._index == variant_npos) || (v._index != __variant_index_of<T, Types...>::value))
throw bad_get("variant get error");
return *reinterpret_cast<T*>(v.getPtr());
}
The code is not "minimal", sorry for that, but if shows my effort. In short: index of type is stored in constructors, there's been also implemented a __variant_index_of
thing, so that index of any given type can be searched in the variant
's template parameters.
It looked like a nice idea then to implement something like:
template<size_t TypeIndex, typename... Types>
using variant_type_getter_t = typename variant_type_getter<TypeIndex, Types...>::type;
Can include it's possible implementation here, but it doesn't solve the problem: occured that _index
is a runtime value, so that templates are of no use, lots of ‘this’ is not a constant expression
errors at compile-time prove that.
Spending pretty lots of time reading the existing sources (like GCC's implementation) lead to a possibly wrong confidence that using classic type erasure (pImpl -> template child parameterized with the stored type) should not be applied here.
Question
So, as the title states: is it even possible to use some trick for implementing access to the type of a variadic template by it's runtime index?
Maybe some template redesign is required for that? Or even maybe the stack allocation of data had not been thought through properly?
Thanks in advance for any bright ideas =)
UPD: renamed the question to emphasize, that I basically know, what are templates, just cannot cook them =(