Info
-
Did you know that C++23 deprecated std::aligned_storage and std::aligned_union?
Example
template<class T>
struct container {
//std::aligned_storage_t<sizeof(T), alignof(T)> t_buff; // deprecated
alignas(T) std::byte t_buff[sizeof(T)]; // okay
};
Puzzle
- Can you implement cointainer which will store aligned union?
union Union {
int i;
double d;
char c;
};
template <class... Ts>
class container; // TODO
int main() {
using namespace boost::ut;
container<int, double, char> container{};
"container"_test = [=] {
mut(container).construct<Union>(Union{.i = 42});
expect(42_i == container.unsafe_get<int>());
mut(container).destroy<Union>();
};
"container"_test = [=] {
mut(container).construct<Union>(Union{.d = 77.});
expect(77._d == container.unsafe_get<double>());
mut(container).destroy<Union>();
};
}
Solutions
template <class... Ts>
class container {
public:
template <class U>
requires std::is_union_v<U>
void construct(U&& u) {
new (buf) U(std::forward<U>(u));
}
template <class U>
requires std::is_union_v<U>
void destroy() {
// No-op since C unions can only be used with
// trivially destructable types that don't need explicit destruction
// To be safe, we can use the below to zero out the memory
std::fill(std::begin(buf), std::end(buf), std::byte{});
}
template <class T>
T unsafe_get() const {
return *reinterpret_cast<const T*>(buf);
}
private:
static constexpr auto largest_elem_size = std::max({sizeof(Ts)...});
private:
alignas(Ts...) std::byte buf[largest_elem_size];
};
template <class... Ts>
union container {};
template <class T, class... Ts>
union container<T, Ts...> {
T _first;
container<Ts...> _rest;
template <class U>
auto construct(const U &value) {
*this = std::bit_cast<container>(value);
}
template <class U>
auto unsafe_get() const {
if constexpr (std::same_as<T, U>) {
return _first;
} else {
return _rest.template unsafe_get<U>();
}
}
template <class U>
auto destroy() {
// :P
}
};
template <typename T, std::size_t Align, std::size_t Size>
concept Fits = alignof(T) <= Align and sizeof(T) <= Size;
template <class... Ts>
class container {
static constexpr auto storage_size = sizeof(std::variant<Ts...>);
static constexpr auto storage_alignment = alignof(std::variant<Ts...>);
alignas(storage_alignment) std::byte storage[storage_size];
std::size_t hash_value{};
std::function<void(void)> destructor;
public:
template <Fits<storage_alignment, storage_size> A, typename... Bs>
void construct(Bs&&... args) {
if (hash_value) {
destructor();
}
new (&storage) A{std::forward<Bs>(args)...};
hash_value = typeid(A).hash_code();
destructor = [this] {
reinterpret_cast<A*>(storage)->~A();
hash_value = 0;
};
}
template <typename T>
T const& unsafe_get() const {
if (hash_value) {
return reinterpret_cast<T const&>(storage);
} else {
throw std::runtime_error(
"Oh no! You want data but there isn't any available.");
}
}
template <Fits<storage_alignment, storage_size> A>
void destroy() {
if (hash_value == typeid(A).hash_code()) {
destructor();
hash_value = 0;
} else {
throw std::runtime_error(
"Yikes!!! You tried to destroy the wrong type!");
}
}
~container() {
if (hash_value) {
destructor();
}
}
};
template <class... Ts>
class container {
static constexpr auto size = std::max({sizeof(Ts)...});
alignas(Ts...) std::array<std::byte, size> buffer;
public:
template <class T>
void construct(T&& value) {
new (buffer.data()) T(std::forward<T>(value));
}
template <class T>
const T unsafe_get() const {
return *reinterpret_cast<const T*>(buffer.data());
}
template <class T>
void destroy() {
buffer.fill(std::byte{});
}
};
template <auto alignment, auto size>
struct data {
alignas(alignment) std::byte bytes[size];
};
template <class... Ts>
class container {
static constexpr std::size_t alignment = std::max({alignof(Ts)...});
static constexpr std::size_t size = std::max({sizeof(Ts)...});
data<alignment, size> *d;
public:
template <class U>
void construct(U u) {
static_assert(sizeof...(Ts) > 0);
static_assert(std::is_union_v<U>);
static_assert(std::max({sizeof(Ts)...}) == sizeof(U));
d = new data<alignment, size>();
const std::byte *source = reinterpret_cast<std::byte *>(&u);
std::memcpy(d->bytes, &u, sizeof(u));
}
template <class T>
T unsafe_get() const {
static_assert((std::is_same_v<T, Ts> || ...), "The type is not valid.");
return *reinterpret_cast<const T *>(&d->bytes);
};
template <class U>
void destroy() const {
delete d;
};
};