An attempt to make a C++11 alternative to C++17 std::any
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Copyright (C) 2020 Vincent Robinson <robinsonvincent89@gmail.com> | |
This program is free software; you can redistribute it and/or modify | |
it under the terms of the GNU Lesser General Public License as published by | |
the Free Software Foundation; either version 2.1 of the License, or | |
(at your option) any later version. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU Lesser General Public License for more details. | |
You should have received a copy of the GNU Lesser General Public License along | |
with this program; if not, write to the Free Software Foundation, Inc., | |
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
*/ | |
#include <iostream> | |
#include <iomanip> | |
#include <string> | |
#define ANY_TEMPLATE(extra) template <typename T, typename std::enable_if< \ | |
(std::is_default_constructible<T>::value && \ | |
std::is_copy_constructible<T>::value && \ | |
std::is_destructible<T>::value) extra>::type* = nullptr> | |
//! An object that can hold any type so long as it has default and copy constructors | |
//! and a default destructor. | |
class Any | |
{ | |
public: | |
//! An ID for any type that Any can hold. Although this is a function pointer, DO | |
//! NOT CALL IT! Bad things will happen if you do. It should be treated as raw data. | |
//! This is not guaranteed to have the same value for each type between compilations. | |
typedef void (*TypeId)(void *); | |
protected: | |
//! Pointer to the Any internal destructor for the currently stored value. | |
//! This also doubles as the type identifier since each type has its own template | |
//! of the destructor function, giving a unique identifier for every possible type. | |
void (*m_destructor)(void *) noexcept = nullptr; | |
//! Pointer to the Any internal copy constructor for the currently stored value. | |
void *(*m_copier)(void *) = nullptr; | |
//! Actual value stored by the class | |
void *m_value = nullptr; | |
public: | |
//! Default constructor creating a void Any | |
Any() noexcept = default; | |
//! Constructor immediately setting the value. This constructor cannot accept | |
//! another Any as an argument as that will be interpreted as the copy constructor. | |
ANY_TEMPLATE() Any(const T &value) | |
{ | |
set<T>(value); | |
} | |
//! Destroys the contained value | |
~Any() noexcept | |
{ | |
try_destruct(); | |
} | |
//! Copy constructor which copies the value by calling the value's copy constructor. | |
Any(const Any &other) | |
{ | |
m_destructor = other.m_destructor; | |
m_copier = other.m_copier; | |
if (m_copier != nullptr) | |
m_value = m_copier(other.m_value); | |
} | |
//! Copy assignment which copies the value by calling the value's copy constructor. | |
Any &operator=(const Any &other) | |
{ | |
try_destruct(); | |
m_destructor = other.m_destructor; | |
m_copier = other.m_copier; | |
if (m_copier != nullptr) | |
m_value = m_copier(other.m_value); | |
return *this; | |
} | |
//! Move constructor; very cheap compared to copy. | |
Any(Any &&other) noexcept | |
{ | |
m_value = other.m_value; | |
m_destructor = other.m_destructor; | |
m_copier = other.m_copier; | |
} | |
//! Move assigment; very cheap compared to copy. | |
Any &operator=(Any &&other) noexcept | |
{ | |
try_destruct(); | |
m_value = other.m_value; | |
m_destructor = other.m_destructor; | |
m_copier = other.m_copier; | |
return *this; | |
} | |
//! Returns the Any's value if the value is of type T. If not, returns `def`. | |
ANY_TEMPLATE() const T &get(const T &def = T()) const noexcept | |
{ | |
if (m_destructor == &Any::destruct<T>) | |
return *static_cast<T *>(m_value); | |
else | |
return def; | |
} | |
//! Sets the value and type of the Any to `value`. To set to void, use `reset`. | |
ANY_TEMPLATE() void set(const T &value) | |
{ | |
try_destruct(); | |
m_value = new T(value); | |
m_destructor = &Any::destruct<T>; | |
m_copier = &Any::copy<T>; | |
} | |
//! Returns true if the Any's value has the type T. | |
ANY_TEMPLATE(|| std::is_void<T>::value) bool has() const noexcept | |
{ | |
return m_destructor == &Any::destruct<T>; | |
} | |
//! Returns true if this Any and `other` have the same type of value. | |
bool sameType(const Any &other) const noexcept | |
{ | |
return m_destructor == other.m_destructor; | |
} | |
//! Sets the Any to a void value. | |
void reset() noexcept | |
{ | |
try_destruct(); | |
} | |
//! Gets the type ID of the currently held data. | |
TypeId getTypeId() const noexcept | |
{ | |
return m_destructor; | |
} | |
//! Gets the type ID for any specified type that Any can hold. | |
ANY_TEMPLATE(|| std::is_void<T>::value) static TypeId getTypeId() noexcept | |
{ | |
return &Any::destruct<T>; | |
} | |
protected: | |
//! Calls the destructor of the value (if any) and sets all pointers to nullptr. | |
void try_destruct() noexcept | |
{ | |
if (m_destructor != nullptr) | |
m_destructor(m_value); | |
m_value = nullptr; | |
m_destructor = nullptr; | |
m_copier = nullptr; | |
} | |
//! Calls the destructor of the value. A direct pointer to the destructor (if even | |
//! possible) would not work since a function pointer to a template is necessary. | |
//! This function is static to reduce function pointer size. | |
ANY_TEMPLATE() static void destruct(void *other) noexcept | |
{ | |
delete static_cast<T *>(other); | |
} | |
//! Calls the copy constructor of the value. See `destruct`'s documentation. | |
ANY_TEMPLATE() static void *copy(void *other) | |
{ | |
return new T(*static_cast<T *>(other)); | |
} | |
}; | |
//! Checks if the Any has a void value. | |
template <> bool Any::has<void>() const noexcept | |
{ | |
return m_value == nullptr; | |
} | |
//! Gets the type ID for a void value. | |
template <> Any::TypeId Any::getTypeId<void>() noexcept | |
{ | |
return nullptr; | |
} | |
#undef ANY_TEMPLATE | |
// Unit testing | |
int main() | |
{ | |
Any a; | |
std::cout << std::boolalpha; | |
#define EFFECT(x) std::cout << std::setw(25) << #x << " -> " << (x) << std::endl | |
#define SIMPLE(x) x; std::cout << std::setw(25) << #x << std::endl; | |
EFFECT(a.has<void>()); | |
SIMPLE(a.set<int>(5)); | |
EFFECT(a.get<int>()); | |
EFFECT(a.get<float>()); | |
EFFECT(a.has<int>()); | |
EFFECT(a.has<std::string>()); | |
EFFECT(a.has<void>()); | |
SIMPLE(a.reset()); | |
EFFECT(a.has<void>()); | |
EFFECT(a.has<int>()); | |
SIMPLE(a.set<int>(10)); | |
SIMPLE(Any b(5)); | |
EFFECT(b.get<int>()); | |
EFFECT(a.sameType(b)); | |
SIMPLE(b.set<float>(0.5)); | |
EFFECT(a.sameType(b)); | |
SIMPLE(Any c(a)); | |
EFFECT(c.get<int>()); | |
SIMPLE(c = b); | |
EFFECT(c.has<float>()); | |
EFFECT(c.get<float>()); | |
SIMPLE(a.set<std::string>("Hello")); | |
SIMPLE(Any d(std::move(a))); | |
EFFECT(d.get<std::string>()); | |
SIMPLE(c.set<bool>(true)); | |
SIMPLE(b = std::move(c)); | |
EFFECT(b.get<bool>()); | |
EFFECT((void *)Any::getTypeId<void>()); | |
EFFECT((void *)Any::getTypeId<bool>()); | |
EFFECT((void *)Any::getTypeId<int>()); | |
EFFECT((void *)Any::getTypeId<float>()); | |
EFFECT(Any::getTypeId<bool>() == b.getTypeId()); | |
EFFECT((void *)Any().getTypeId()); | |
std::cout << "Success" << std::endl; | |
#undef SIMPLE | |
#undef EFFECT | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment