I've written my own std::any implementation that has configurable small value optimization (STL has no configuration for this and default values are very small, e. g. in GCC it's just one pointer size).
#pragma once
#include <cstddef>
#include <cstring>
#include <typeinfo>
#include <new>
#include <utility>
template<std::size_t STORAGE_SIZE> class Any {
typedef void (*CopyFunc)(void *dst, const void *src);
typedef void (*MoveFunc)(void *dst, void *src);
typedef void(*DestroyFunc)(void *val);
struct Operations {
const std::type_info &typeInfo;
std::size_t size;
std::size_t align;
CopyFunc copy;
MoveFunc move;
DestroyFunc destroy;
};
template<typename T> static inline constexpr Operations OPERATIONS = {
.typeInfo = typeid(T),
.size = ([]() {
if constexpr (std::is_same_v<T, void>) {
return 0;
} else {
return sizeof(T);
}
})(),
.align = ([]() {
if constexpr (std::is_same_v<T, void>) {
return 0;
} else {
return alignof(T);
}
})(),
.copy = ([]() {
if constexpr (std::is_same_v<T, void> || std::is_trivially_copy_constructible_v<T>) {
return nullptr;
} else {
return static_cast<CopyFunc>([](void *dst, const void *src) {
new (dst) T(*reinterpret_cast<const T*>(src));
});
}
})(),
.move = ([]() {
if constexpr (std::is_same_v<T, void> || std::is_trivially_move_constructible_v<T>) {
return nullptr;
} else {
return static_cast<MoveFunc>([](void *dst, void *src) {
new (dst) T(std::move(*reinterpret_cast<T*>(src)));
});
}
})(),
.destroy = ([]() {
if constexpr (std::is_same_v<T, void> || std::is_trivially_destructible_v<T>) {
return nullptr;
} else {
return static_cast<DestroyFunc>([](void *arg) {
reinterpret_cast<T*>(arg)->~T();
});
}
})()
};
std::size_t m_storage[(STORAGE_SIZE sizeof(std::size_t) - 1) / sizeof(std::size_t)];
void *m_heapStorage = nullptr;
const Operations *m_operations = &OPERATIONS<void>;
public:
Any() = default;
template<typename T> Any(const T &value) {
if constexpr (sizeof(T) > sizeof(m_storage) || alignof(T) > alignof(std::size_t)) {
m_heapStorage = new T(value);
} else {
new (m_storage) T(value);
}
m_operations = &OPERATIONS<T>;
}
template<typename T> Any(T &&value) noexcept {
if constexpr (sizeof(T) > sizeof(m_storage) || alignof(T) > alignof(std::size_t)) {
m_heapStorage = new T(std::move(value));
} else {
new (m_storage) T(std::move(value));
}
m_operations = &OPERATIONS<T>;
}
template<std::size_t N> Any(const Any<N> &value) {
if (value.m_heapStorage || value.m_operations->size > sizeof(m_storage)) {
m_heapStorage = new (std::align_val_t(value.m_operations->align)) char[value.m_operations->size];
if (value.m_operations) {
value.m_operations->copy(m_heapStorage, value.m_heapStorage ? value.m_heapStorage : value.m_storage);
} else {
std::memcpy(
m_heapStorage,
value.m_heapStorage ? value.m_heapStorage : value.m_storage,
value.m_operations->size
);
}
} else {
if (value.m_operations->copy) {
value.m_operations->copy(m_storage, value.m_storage);
} else {
std::memcpy(m_storage, value.m_storage, value.m_operations->size);
}
}
m_operations = value.m_operations;
}
template<std::size_t N> Any(Any<N> &&value) noexcept {
m_operations = value.m_operations;
if (value.m_heapStorage) {
m_heapStorage = value.m_heapStorage;
value.m_heapStorage = nullptr;
value.m_operations = &OPERATIONS<void>;
} else {
if (value.m_operations->size > STORAGE_SIZE) {
m_heapStorage = new (std::align_val_t(value.m_operations->align)) char[value.m_operations->size];
}
if (m_operations->move) {
m_operations->move(m_heapStorage ? m_heapStorage : m_storage, value.m_storage);
} else {
std::memcpy(m_heapStorage ? m_heapStorage : m_storage, value.m_storage, value.m_operations->size);
}
}
}
~Any() {
if (m_operations->destroy) {
m_operations->destroy(m_heapStorage ? m_heapStorage : m_storage);
}
if (m_heapStorage) {
delete static_cast<char*>(m_heapStorage);
}
}
[[nodiscard]] const std::type_info &type() const {
return m_operations->typeInfo;
}
[[nodiscard]] std::size_t size() const {
return m_operations->size;
}
[[nodiscard]] std::size_t align() const {
return m_operations->align;
}
[[nodiscard]] bool isHeapAllocated() const {
return m_heapStorage != nullptr;
}
template<typename T> [[nodiscard]] const T *get() const {
if (m_operations->typeInfo == typeid(T)) {
return reinterpret_cast<const T*>(m_heapStorage ? m_heapStorage : m_storage);
} else {
return nullptr;
}
}
template<typename T> [[nodiscard]] T *get() {
if (m_operations->typeInfo == typeid(T)) {
return reinterpret_cast<T*>(m_heapStorage ? m_heapStorage : m_storage);
} else {
return nullptr;
}
}
void clear() {
if (m_operations->destroy) {
m_operations->destroy(m_heapStorage ? m_heapStorage : m_storage);
}
if (m_heapStorage) {
delete static_cast<char*>(m_heapStorage);
m_heapStorage = nullptr;
}
m_operations = &OPERATIONS<void>;
}
template<typename T, typename... Args> void emplace(Args &&...args) {
clear();
if constexpr (sizeof(T) > sizeof(m_storage) || alignof(T) > alignof(std::size_t)) {
m_heapStorage = new T(std::forward<Args...>(args)...);
} else {
new (m_storage) T(std::forward<Args...>(args)...);
}
m_operations = &OPERATIONS<T>;
}
template<std::size_t N> Any<STORAGE_SIZE> &operator=(const Any<N> &value) {
clear();
if (value.m_heapStorage || value.m_operations->size > sizeof(m_storage)) {
m_heapStorage = new (std::align_val_t(value.m_operations->align)) char[value.m_operations->size];
if (value.m_operations) {
value.m_operations->copy(m_heapStorage, value.m_heapStorage ? value.m_heapStorage : value.m_storage);
} else {
std::memcpy(
m_heapStorage,
value.m_heapStorage ? value.m_heapStorage : value.m_storage,
value.m_operations->size
);
}
} else {
if (value.m_operations->copy) {
value.m_operations->copy(m_storage, value.m_storage);
} else {
std::memcpy(m_storage, value.m_storage, value.m_operations->size);
}
}
m_operations = value.m_operations;
return *this;
}
template<std::size_t N> Any<STORAGE_SIZE> &operator=(Any<N> &&value) noexcept {
clear();
m_operations = value.m_operations;
if (value.m_heapStorage) {
m_heapStorage = value.m_heapStorage;
value.m_heapStorage = nullptr;
value.m_operations = &OPERATIONS<void>;
} else {
if (value.m_operations->size > STORAGE_SIZE) {
m_heapStorage = new (std::align_val_t(value.m_operations->align)) char[value.m_operations->size];
}
if (m_operations->move) {
m_operations->move(m_heapStorage ? m_heapStorage : m_storage, value.m_storage);
} else {
std::memcpy(m_heapStorage ? m_heapStorage : m_storage, value.m_storage, value.m_operations->size);
}
}
return *this;
}
template<typename T> Any &operator=(T &&value) noexcept {
using TT = typename std::decay<T>::type;
emplace<TT>(std::forward<T>(value));
return *this;
}
};
But I have problem with some constructors:
int x = 10;
Any<64> v0 = x; // Error
Any<64> v1 = 10; // Works
Any<128> v2 = v1; // Error
In both error cases compiler picks template<typename T> Any(T &&value)
instead of template<typename T> Any(const T &value)
for the first case and template<std::size_t N> Any(const Any<N> &value)
for the second.
How constructors overloading can be fixed?
P. S.: There is the same problem for an assignment operator when we pass Any
instead of some value (for value everything works fine).
CodePudding user response:
The main issue is that template<typename T> Any(T &&value)
is a forwarding/universal reference and can accept anything. In certain situations it might prefer this constructor over any kind of constructor from Any
. So I'd recommend conditioning it so it won't be an Any
. Also, since it accepts any input you can unify it with the const T&
constructor into a single one - just use std::forward
.
template<typename T, std::enable_if_t<!is_Any_v<std::remove_reference_t<std::remove_const_t<T> > >, int> = 0>
Any(T &&value) noexcept {
if constexpr (sizeof(T) > sizeof(m_storage) || alignof(T) > alignof(std::size_t)) {
m_heapStorage = new T(std::forward<T>(value));
} else {
new (m_storage) T(std::forward<T>(value));
}
m_operations = &OPERATIONS<T>;
}
whereas is_Any
is defined above the class definition as:
template<std::size_t STORAGE_SIZE> class Any;
template<typename T>
struct is_Any : std::false_type {};
template<std::size_t STORAGE_SIZE>
struct is_Any<Any<STORAGE_SIZE>> : std::true_type {};
template<typename T>
constexpr bool is_Any_v = is_Any<T>::value;
CodePudding user response:
You can simply enable_if
non-references:
template<typename T, std::enable_if_t<!std::is_reference_v<T>, bool> = true>
Any(T &&value) noexcept {
// ...
Note that you will also need to move Operations
out of the class.