Reputation: 1098
I have a function template like the following:
template<class U, class T>
T* unsafeCast(U* theUnion) {
reinterpret_cast<T*>(theUnion);
}
How can I make sure this only compiles if T is a type contained within the union U, so that the following holds?
union FooUnion {
int a;
double b;
} foo;
unsafeCast<FooUnion, int>(&foo); // compiles
unsafeCast<FooUnion, double>(&foo); // compiles
unsafeCast<FooUnion, char>(&foo); // does not compile
I understand that is_union
from <type_traits>
allows to check for a union, but how can I check for types within a union?
Upvotes: 1
Views: 486
Reputation: 2001
If you can add constructors to your unions you can do it like this:
#include <type_traits>
// Use std::void_t to only allow types that the union can be constructed from
template<class T, class U, class = std::void_t<decltype(U{std::declval<T>()})>>
T* unsafeCast(U* theUnion) {
return reinterpret_cast<T*>(theUnion);
}
union FooUnion {
int a;
double b;
// Explictly add constructors for each desired type
FooUnion(int a) : a{a} {}
FooUnion(double b) : b{b} {}
// Add a deleted catch all to prevent implicit conversions, e.g. from char
template <class T>
FooUnion(T) = delete;
} foo(0);
int main() {
unsafeCast<int>(&foo); // compiles
unsafeCast<double>(&foo); // compiles
unsafeCast<char>(&foo); // does not compile
}
Upvotes: 0
Reputation: 275820
You cannot.
boost::variant and std::variant are solutions to this problem such that the union carries with it the type information you need.
You could create a raw union like this:
template<class T>
struct data_holder {
T data;
};
template<class...Ts>
struct union_data;
template<>
struct union_data<>{};
template<class T0>
struct union_data<T0>:data_holder<T0> {};
template<class T0, class...Ts>
struct union_data<T0, Ts...> {
union {
union_data<T0> lhs;
union_data<Ts...> rhs;
};
};
template<class...Ts>
struct raw_union:union_data<Ts...>{
template<class T>
constexpr static bool valid_type() {
return (std::is_same<T, Ts>{}||...); // rewrite in C++14/11
}
template<class T>
union_data<T>* get_data_ptr() {
static_assert( valid_type<T>() );
return reinterpret_cast<union_data<T>*>(this);
}
template<class T>
union_data<T> const* get_data_ptr() const{
static_assert( valid_type<T>() );
return reinterpret_cast<union_data<T> const*>(this);
}
template<class T>
T& get_unsafe() {
return get_data_ptr<T>()->data;
}
template<class T>
T const& get_unsafe() const {
return get_data_ptr<T>()->data;
}
template<class T, class...Us>
T& emplace( Us&&... us ) {
auto* ptr = ::new( (void*)get_data_ptr<T>() ) union_data<T>{ T(std::forward<Us>(us)...) };
return ptr->data;
}
template<class T>
void dtor() {
get_data_ptr<T>()->~T();
}
};
which is unsafe and undiscriminated, but does check if foo.get_unsafe<int>()
actually contains an int
.
Use:
raw_union<int, double> un;
un.emplace<int>(7);
std::cout << un.get_unsafe<int>() << "\n";
it does not support multiple union members of the same type. You are in charge of calling .emplace<T>(x)
before using T
, and if a non-trivial destructor .dtor<T>()
.
Accessing members that are not active is just as perilous as doing so with raw C/C++ unions.
Upvotes: 1