24
\$\begingroup\$

Today, I decided to implementstd::any using thecppreference page. I've never actually usedstd::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;  };};} // mystdtemplate <>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++20template <typename T>using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;// any_casttemplate <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_anytemplate <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...

askedApr 25, 2019 at 6:23
Indiana Kernick's user avatar
\$\endgroup\$
3
  • 4
    \$\begingroup\$There's nothing much to say except it's excellent. As to why usestd::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\$CommentedApr 25, 2019 at 8:07
  • \$\begingroup\$Inany_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\$CommentedOct 7, 2021 at 12:20
  • 1
    \$\begingroup\$@Riley I’m not checkinganything 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\$CommentedOct 7, 2021 at 21:09

2 Answers2

15
\$\begingroup\$

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

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

Non-conformance (priority: high)

  1. Thou Shalt Not Specializestd::swap. Instead, you should overloadswap to be found by ADL. SeeHow to overloadstd::swap() on Stack Overflow.

    class any {public:  // ...  friend void swap(any& lhs, any& rhs)  {    lhs.swap(rhs);  }};
  2. [any.bad_any_cast]/2 specifies thatbad_any_cast should derive fromstd::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 anint. —end example ] Such small-object optimization shall only be applied to typesT for whichis_nothrow_move_constructible_v<T> istrue.

    Clearly, you did not implement this optimization.

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

    ~any() = default;
  3. The followingstatic_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 unlessstd::is_copy_constructible_v<std::decay_t<ValueType>>.

answeredApr 27, 2019 at 4:31
L. F.'s user avatar
\$\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\$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 ofany, checkboost::any. It has a very readable code and implements both: small value optimization & RTTI without built-in support(i.e. not usingvirtual nortypeid keywords). More interestingly, there is aboost::anys::basic_any template class who enables you to decide how big the small values to optimize can be.

regards,

FM.

answeredJun 30, 2022 at 11:43
Red.Wave's user avatar
\$\endgroup\$

You mustlog in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.