Today, I decided to implement std::any
using the cppreference page. I've never actually used std::any
before and after seeing the implementation first hand... I don't think I'll start now! I'm not entirely sure what this class is actually meant for. I'm not even sure why I implemented this in the first place...
Anyway, here's the code:
#include <memory>
#include <utility>
#include <typeinfo>
namespace mystd {
template <typename T>
struct is_in_place_type : std::false_type {};
template <typename T>
struct is_in_place_type<std::in_place_type_t<T>> : std::true_type {};
class any {
template <typename ValueType>
friend const ValueType *any_cast(const any *) noexcept;
template <typename ValueType>
friend ValueType *any_cast(any *) noexcept;
public:
// constructors
constexpr any() noexcept = default;
any(const any &other) {
if (other.instance) {
instance = other.instance->clone();
}
}
any(any &&other) noexcept
: instance(std::move(other.instance)) {}
template <typename ValueType, typename = std::enable_if_t<
!std::is_same_v<std::decay_t<ValueType>, any> &&
!is_in_place_type<std::decay_t<ValueType>>::value &&
std::is_copy_constructible_v<std::decay_t<ValueType>>
>>
any(ValueType &&value) {
static_assert(std::is_copy_constructible_v<std::decay_t<ValueType>>, "program is ill-formed");
emplace<std::decay_t<ValueType>>(std::forward<ValueType>(value));
}
template <typename ValueType, typename... Args, typename = std::enable_if_t<
std::is_constructible_v<std::decay_t<ValueType>, Args...> &&
std::is_copy_constructible_v<std::decay_t<ValueType>>
>>
explicit any(std::in_place_type_t<ValueType>, Args &&... args) {
emplace<std::decay_t<ValueType>>(std::forward<Args>(args)...);
}
template <typename ValueType, typename List, typename... Args, typename = std::enable_if_t<
std::is_constructible_v<std::decay_t<ValueType>, std::initializer_list<List> &, Args...> &&
std::is_copy_constructible_v<std::decay_t<ValueType>>
>>
explicit any(std::in_place_type_t<ValueType>, std::initializer_list<List> list, Args &&... args) {
emplace<std::decay_t<ValueType>>(list, std::forward<Args>(args)...);
}
// assignment operators
any &operator=(const any &rhs) {
any(rhs).swap(*this);
return *this;
}
any &operator=(any &&rhs) noexcept {
any(std::move(rhs)).swap(*this);
return *this;
}
template <typename ValueType>
std::enable_if_t<
!std::is_same_v<std::decay_t<ValueType>, any> &&
std::is_copy_constructible_v<std::decay_t<ValueType>>,
any &
>
operator=(ValueType &&rhs) {
any(std::forward<ValueType>(rhs)).swap(*this);
return *this;
}
// modifiers
template <typename ValueType, typename... Args>
std::enable_if_t<
std::is_constructible_v<std::decay_t<ValueType>, Args...> &&
std::is_copy_constructible_v<std::decay_t<ValueType>>,
std::decay_t<ValueType> &
>
emplace(Args &&... args) {
auto new_inst = std::make_unique<storage_impl<std::decay_t<ValueType>>>(std::forward<Args>(args)...);
std::decay_t<ValueType> &value = new_inst->value;
instance = std::move(new_inst);
return value;
}
template <typename ValueType, typename List, typename... Args>
std::enable_if_t<
std::is_constructible_v<std::decay_t<ValueType>, std::initializer_list<List> &, Args...> &&
std::is_copy_constructible_v<std::decay_t<ValueType>>,
std::decay_t<ValueType> &
>
emplace(std::initializer_list<List> list, Args &&... args) {
auto new_inst = std::make_unique<storage_impl<std::decay_t<ValueType>>>(list, std::forward<Args>(args)...);
std::decay_t<ValueType> &value = new_inst->value;
instance = std::move(new_inst);
return value;
}
void reset() noexcept {
instance.reset();
}
void swap(any &other) noexcept {
std::swap(instance, other.instance);
}
// observers
bool has_value() const noexcept {
return static_cast<bool>(instance);
}
const std::type_info &type() const noexcept {
return instance ? instance->type() : typeid(void);
}
private:
struct storage_base;
std::unique_ptr<storage_base> instance;
struct storage_base {
virtual ~storage_base() = default;
virtual const std::type_info &type() const noexcept = 0;
virtual std::unique_ptr<storage_base> clone() const = 0;
};
template <typename ValueType>
struct storage_impl final : public storage_base {
template <typename... Args>
storage_impl(Args &&... args)
: value(std::forward<Args>(args)...) {}
const std::type_info &type() const noexcept override {
return typeid(ValueType);
}
std::unique_ptr<storage_base> clone() const override {
return std::make_unique<storage_impl<ValueType>>(value);
}
ValueType value;
};
};
} // mystd
template <>
void std::swap(mystd::any &lhs, mystd::any &rhs) noexcept {
lhs.swap(rhs);
}
namespace mystd {
class bad_any_cast : public std::exception {
public:
const char *what() const noexcept {
return "bad any cast";
}
};
// C++20
template <typename T>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
// any_cast
template <typename ValueType>
ValueType any_cast(const any &anything) {
using value_type_cvref = remove_cvref_t<ValueType>;
static_assert(std::is_constructible_v<ValueType, const value_type_cvref &>, "program is ill-formed");
if (auto *value = any_cast<value_type_cvref>(&anything)) {
return static_cast<ValueType>(*value);
} else {
throw bad_any_cast();
}
}
template <typename ValueType>
ValueType any_cast(any &anything) {
using value_type_cvref = remove_cvref_t<ValueType>;
static_assert(std::is_constructible_v<ValueType, value_type_cvref &>, "program is ill-formed");
if (auto *value = any_cast<value_type_cvref>(&anything)) {
return static_cast<ValueType>(*value);
} else {
throw bad_any_cast();
}
}
template <typename ValueType>
ValueType any_cast(any &&anything) {
using value_type_cvref = remove_cvref_t<ValueType>;
static_assert(std::is_constructible_v<ValueType, value_type_cvref>, "program is ill-formed");
if (auto *value = any_cast<value_type_cvref>(&anything)) {
return static_cast<ValueType>(std::move(*value));
} else {
throw bad_any_cast();
}
}
template <typename ValueType>
const ValueType *any_cast(const any *anything) noexcept {
if (!anything) return nullptr;
auto *storage = dynamic_cast<any::storage_impl<ValueType> *>(anything->instance.get());
if (!storage) return nullptr;
return &storage->value;
}
template <typename ValueType>
ValueType *any_cast(any *anything) noexcept {
return const_cast<ValueType *>(any_cast<ValueType>(static_cast<const any *>(anything)));
}
// make_any
template <typename ValueType, typename... Args>
any make_any(Args &&... args) {
return any(std::in_place_type<ValueType>, std::forward<Args>(args)...);
}
template <typename ValueType, typename List, typename... Args>
any make_any(std::initializer_list<List> list, Args &&... args) {
return any(std::in_place_type<ValueType>, list, std::forward<Args>(args)...);
}
} // mystd
I'm thinking about doing this in C++11 without rigorously adhering to the standard and without RTTI. Maybe another day...
std::any
, I'd say it's rarely useful, because if you know the possible type values you'd usestd::variant
, and if not you'll be embarrassed to cast it back to a usable value / pointer. Besides, C++ programmers are used to avoid RTTI and Java-like hierarchies under a very abstractObject
type. Nonetheless it can find a use in evolutive / pluggable / distributed programs, where different components can try and recognize what astd::any
really is. \$\endgroup\$any_cast
of a pointer, you check if the pointer is null beforedynamic_cast
, and then again after. Butdynamic_cast<T>(nullptr)
just gives back a null pointer of typeT
(en.cppreference.com/w/cpp/language/dynamic_cast item 2), so these two checks can be merged. Is there a reason to keep them separate, such as clarity or optimization? \$\endgroup\$anything
and castinganything
. I’m checkinganything
and castinganything->storage
. If either of the null checks are removed then I’d be dereferencing a null pointer. \$\endgroup\$