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\$