Skip to content

Instantly share code, notes, and snippets.

@v-rob
Last active September 15, 2020 04:33
Show Gist options
  • Save v-rob/935510aa7c02d1a489258815f05dcb4a to your computer and use it in GitHub Desktop.
Save v-rob/935510aa7c02d1a489258815f05dcb4a to your computer and use it in GitHub Desktop.
An attempt to make a C++11 alternative to C++17 std::any
/*
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