Took a shot at implementing std::any. I think this should cover a majority of the functionality of the standard version, except there may be some missing auxiliary functions, and the overload resolution may not be exactly as is written in the standard.
#include <memory>
#include <typeinfo>
#include <utility>
#include <stdexcept>
struct bad_any_cast: std::exception {
const char* what() const noexcept override {
return "bad_any_cast: failed type conversion using AnyCast";
}
};
class Any {
public:
constexpr Any() noexcept = default;
template<typename T, typename Decayed = std::decay_t<T>>
requires(!std::is_same_v<Decayed, Any>)
Any(T&& value) {
m_holder = std::make_unique<holder<Decayed>>(std::forward<T>(value));
}
template<typename T, typename... Args>
Any(std::in_place_type_t<T> type, Args&&...args) {
emplace<T>(std::forward<Args>(args)...);
}
Any(const Any& other) {
if (other.m_holder) {
m_holder = std::unique_ptr<base_holder>(other.m_holder->clone());
}
}
Any& operator=(const Any& other) {
if (this != &other) {
Any temp{other};
swap(temp);
}
return *this;
}
Any(Any&& other) noexcept : m_holder(std::move(other.m_holder)) {}
Any& operator=(Any&& other) noexcept {
if (this != &other) {
m_holder = std::move(other.m_holder);
}
return *this;
}
template<typename T, typename... Args>
void emplace(Args&&... args) {
m_holder = std::make_unique<holder<T>>(std::forward<Args>(args)...);
}
void swap(Any& other) noexcept {
std::swap(m_holder, other.m_holder);
}
void reset() noexcept {
m_holder.reset();
}
bool has_value() const noexcept {
return bool(*this);
}
explicit operator bool() const noexcept {
return m_holder != nullptr;
}
~Any() = default;
private:
template<typename U>
friend U AnyCast(const Any& value);
template<typename U>
friend U AnyCast(Any& value);
template<typename U>
friend U AnyCast(Any&& value);
template<typename U>
friend U* AnyCast(Any* value);
struct base_holder {
virtual const std::type_info &type() const noexcept = 0;
virtual base_holder* clone() const = 0;
virtual ~base_holder() = default;
};
template<typename T>
struct holder: base_holder {
template<typename U>
holder(U&& value):
item{std::forward<U>(value)} {}
template<typename... Args>
holder(Args&&... args):
item{T(std::forward<Args>(args)...)} {}
holder* clone() const override {
return new holder<T>{std::move(item)};
}
const std::type_info &type() const noexcept override {
return typeid(T);
}
T item;
};
std::unique_ptr<base_holder> m_holder;
};
template<typename T>
T AnyCast(const Any& value) {
if (value.m_holder->type() != typeid(T)) {
throw bad_any_cast();
}
return dynamic_cast<Any::holder<T>&>(*value.m_holder).item;
}
template<typename T>
T AnyCast(Any&& value) {
if (value.m_holder->type() != typeid(T)) {
throw bad_any_cast();
}
return dynamic_cast<Any::holder<T>&>(*value.m_holder).item;
}
template<typename T>
T AnyCast(Any& value) {
if (value.m_holder->type() != typeid(T)) {
throw bad_any_cast();
}
return dynamic_cast<Any::holder<T>&>(*value.m_holder).item;
}
template<typename T>
T* AnyCast(Any* value) {
if (!value || value->m_holder->type() != typeid(T)) {
return nullptr;
}
return &(dynamic_cast<Any::holder<T>&>(*value->m_holder).item);
}
template<typename T, typename...Args>
Any makeAny(Args&&... args) {
return Any(std::in_place_type<T>, std::forward<Args>(args)...);
}
The class is very popular with lots of friends, most of which do very similar things. Is there some way I can consolidate this? Why is any_cast a separate function and not part of the class anyway?
Finally, some tests partially inspired by cppreference examples.
#ifdef BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#else
#include <boost/test/included/unit_test.hpp>
#endif // BOOST_TEST_DYN_LINK
#include <boost/test/data/monomorphic.hpp>
#include <boost/test/data/test_case.hpp>
#include "Any.h"
struct A {
int age;
std::string name;
double salary;
A(int age, std::string name, double salary)
: age(age), name(std::move(name)), salary(salary) {}
};
BOOST_AUTO_TEST_CASE(default_constructor_test) {
Any a;
BOOST_CHECK(!a);
BOOST_CHECK(!a.has_value());
}
BOOST_AUTO_TEST_CASE(basic_int_test) {
Any a{7};
BOOST_CHECK_EQUAL(AnyCast<int>(a), 7);
BOOST_CHECK(a.has_value());
}
BOOST_AUTO_TEST_CASE(in_place_type_test) {
Any a(std::in_place_type<A>, 30, "Ada", 1000.25);
BOOST_CHECK_EQUAL(AnyCast<A>(a).age, 30);
BOOST_CHECK_EQUAL(AnyCast<A>(a).name, "Ada");
BOOST_CHECK_EQUAL(AnyCast<A>(a).salary, 1000.25);
}
BOOST_AUTO_TEST_CASE(bad_cast_test) {
Any a{7};
BOOST_CHECK_THROW(AnyCast<float>(a), bad_any_cast);
}
BOOST_AUTO_TEST_CASE(type_change_test) {
Any a{7};
BOOST_CHECK_EQUAL(AnyCast<int>(a), 7);
BOOST_CHECK_THROW(AnyCast<std::string>(a), bad_any_cast);
a = std::string("hi");
BOOST_CHECK_EQUAL(AnyCast<std::string>(a), "hi");
BOOST_CHECK_THROW(AnyCast<int>(a), bad_any_cast);
}
BOOST_AUTO_TEST_CASE(reset_test) {
Any a{7};
BOOST_CHECK_EQUAL(AnyCast<int>(a), 7);
a.reset();
BOOST_CHECK(!a.has_value());
}
BOOST_AUTO_TEST_CASE(pointer_test) {
Any a{7};
int *i = AnyCast<int>(&a);
BOOST_CHECK_EQUAL(*i, 7);
}
BOOST_AUTO_TEST_CASE(emplacement_test) {
Any a;
BOOST_CHECK(!a.has_value());
a.emplace<A>(30, "Ada", 1000.25);
BOOST_CHECK_EQUAL(AnyCast<A>(a).age, 30);
BOOST_CHECK_EQUAL(AnyCast<A>(a).name, "Ada");
BOOST_CHECK_EQUAL(AnyCast<A>(a).salary, 1000.25);
a.emplace<A>(50, "Bob", 500.5);
BOOST_CHECK_EQUAL(AnyCast<A>(a).age, 50);
BOOST_CHECK_EQUAL(AnyCast<A>(a).name, "Bob");
BOOST_CHECK_EQUAL(AnyCast<A>(a).salary, 500.5);
}
BOOST_AUTO_TEST_CASE(swap_test) {
Any a1{7};
Any a2{A{30, "Ada", 1000.25}};
a1.swap(a2);
BOOST_CHECK_EQUAL(AnyCast<int>(a2), 7);
BOOST_CHECK_EQUAL(AnyCast<A>(a1).age, 30);
BOOST_CHECK_EQUAL(AnyCast<A>(a1).name, "Ada");
BOOST_CHECK_CLOSE(AnyCast<A>(a1).salary, 1000.25, 0.0001); // Use BOOST_CHECK_CLOSE for floating point comparisons
}
BOOST_AUTO_TEST_CASE(make_any_test) {
Any a1 = makeAny<int>(7);
Any a2 = makeAny<A>(30, "Ada", 1000.25);
a1.swap(a2);
BOOST_CHECK_EQUAL(AnyCast<int>(a2), 7);
BOOST_CHECK_EQUAL(AnyCast<A>(a1).age, 30);
BOOST_CHECK_EQUAL(AnyCast<A>(a1).name, "Ada");
BOOST_CHECK_EQUAL(AnyCast<A>(a1).salary, 1000.25);
}
BOOST_AUTO_TEST_CASE(move_test) {
Any a1{42};
Any a2{std::move(a1)};
BOOST_CHECK(!a1.has_value());
BOOST_CHECK_EQUAL(AnyCast<int>(a2), 42);
}
BOOST_AUTO_TEST_CASE(copy_test) {
Any original{A{30, "Ada", 1000.25}};
Any copy{original};
A original_casted = AnyCast<A>(original);
A copy_casted = AnyCast<A>(copy);
original_casted.age = 40;
BOOST_CHECK_NE(original_casted.age, copy_casted.age);
}
BOOST_AUTO_TEST_CASE(self_assignment_test) {
Any a{5};
a = a; // self-assignment
BOOST_CHECK_EQUAL(AnyCast<int>(a), 5);
}
BOOST_AUTO_TEST_CASE(nested_any_test) {
Any inner{42};
Any outer;
outer.emplace<Any>(inner);
BOOST_CHECK_THROW(AnyCast<int>(outer), bad_any_cast);
BOOST_CHECK_EQUAL(AnyCast<Any>(outer).has_value(), true);
}
BOOST_AUTO_TEST_CASE(large_object_test) {
std::vector<int> largeVector(1000000, 5); // A vector with 1 million ints.
Any a{largeVector};
BOOST_CHECK_EQUAL(AnyCast<std::vector<int>>(a).size(), 1000000);
}
static int destructorCounter = 0;
struct TestDestruction {
~TestDestruction() {
destructorCounter++;
}
};
BOOST_AUTO_TEST_CASE(destructor_call_test) {
{
Any a;
a.emplace<TestDestruction>();
} // scope to ensure a is destroyed
BOOST_CHECK_EQUAL(destructorCounter, 1);
}
struct ExceptionThrower {
ExceptionThrower() {
throw std::runtime_error("Exception during construction");
}
};
BOOST_AUTO_TEST_CASE(exception_safety_test) {
BOOST_CHECK_THROW(Any a{ExceptionThrower{}}, std::runtime_error);
}
```