23
\$\begingroup\$

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...

\$\endgroup\$
3
  • 4
    \$\begingroup\$There's nothing much to say except it's excellent. As to why use std::any, I'd say it's rarely useful, because if you know the possible type values you'd use std::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 abstract Object type. Nonetheless it can find a use in evolutive / pluggable / distributed programs, where different components can try and recognize what a std::any really is.\$\endgroup\$
    – papagaga
    CommentedApr 25, 2019 at 8:07
  • \$\begingroup\$In any_cast of a pointer, you check if the pointer is null before dynamic_cast, and then again after. But dynamic_cast<T>(nullptr) just gives back a null pointer of type T (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\$
    – Riley
    CommentedOct 7, 2021 at 12:20
  • 1
    \$\begingroup\$@Riley I’m not checking anything and casting anything. I’m checking anything and casting anything->storage. If either of the null checks are removed then I’d be dereferencing a null pointer.\$\endgroup\$CommentedOct 7, 2021 at 21:09

2 Answers 2

14
\$\begingroup\$

Your implementation is excellent! I can hardly find any problems. I was amazed how simple a conforming implementation of any can be. And I wholeheartedly agree with @papagaga's comment.

Here's my two cents. I use the N4659, the C++17 final draft, as a reference.

Non-conformance (priority: high)

  1. Thou Shalt Not Specialize std::swap. Instead, you should overload swap to be found by ADL. See How to overload std::swap() on Stack Overflow.

    class any { public: // ... friend void swap(any& lhs, any& rhs) { lhs.swap(rhs); } }; 
  2. [any.bad_any_cast]/2 specifies that bad_any_cast should derive from std::bad_cast. Your implementation fails to do this.

Other suggestions (priority: low)

  1. [any.class]/3 says:

    Implementations should avoid the use of dynamically allocated memory for a small contained value. [ Example: where the object constructed is holding only an int. — end example ] Such small-object optimization shall only be applied to types T for which is_nothrow_move_constructible_v<T> is true.

    Clearly, you did not implement this optimization.

  2. Initially I thought, "where is your destructor?" Then I realized that the synthesized destructor is equivalent to reset(). I recommend you explicitly default this to reduce confusion since you implemented the rest of the Big Five.

    ~any() = default; 
  3. The following static_assert on line 40 is unnecessary:

    static_assert(std::is_copy_constructible_v<std::decay_t<ValueType>>, "program is ill-formed"); 

    because this constructor does not participate in overload resolution unless std::is_copy_constructible_v<std::decay_t<ValueType>>.

\$\endgroup\$
2
  • 2
    \$\begingroup\$I was hoping for a review that mentioned nonconformance! Specializing the swap template felt a bit weird. I overloaded swap in the past but I guess just I forgot this time. The cppreference page does say that bad_any_cast derives from bad_cast so my fault for not reading carefully. I've never implemented SBO before so I wasn't sure how to do it. I do remember explicitly defaulting the destructor at some point but I guess I deleted it when reordering things (oops). I knew the static_assert was redundant but the cppreference page mentions "program is ill-formed" so I did it anyway! Great review!\$\endgroup\$CommentedApr 27, 2019 at 4:45
  • \$\begingroup\$@Kerndog73 Thank you! Hope you don't liberally forget things in the future ;-) About SBO: it's not that hard. You can just specialize the template for types that meet some criteria (e.g., sizeof(T) is less than some threshold) and implement it accordingly.\$\endgroup\$
    – L. F.
    CommentedApr 27, 2019 at 4:47
0
\$\begingroup\$

It is good exercise to try to implement std features yourself. But if you want an elegant implementation of any, check boost::any. It has a very readable code and implements both: small value optimization & RTTI without built-in support(i.e. not using virtual nor typeid keywords). More interestingly, there is a boost::anys::basic_any template class who enables you to decide how big the small values to optimize can be.

regards,

FM.

\$\endgroup\$

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.