C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Get base class from std::any

From: Phil Endecott <std_proposals_list_at_[hidden]>
Date: Sun, 26 Mar 2023 20:11:28 +0100
Edward Catmur wrote:
> On Sun, 26 Mar 2023, 13:29 Phil Endecott via Std-Proposals, <
> std-proposals_at_[hidden]> wrote:
>
>> Dear Experts,
>>
>> #include <any>
>> #include <cassert>
>>
>> class Base {};
>> class Derived: public Base {};
>>
>> void f()
>> {
>> Derived d;
>> std::any a{ d };
>> auto p = std::any_cast<Base>(&a);
>> assert(p); // Fails
>> }
>>
>>
>> To access a std::any's contained value we need to know its exact type;
>> we cannot access it knowing only a base type.
>>
>> What would it take to make this possible?
>>
>> Essentially, if we imagine std::any to be something like:
>>
>> class any
>> {
>> type_info type;
>> void* ptr;
>> };
>>
>> So we could add something like:
>>
>> template <typename T>
>> T* any_base_cast(any* a)
>> {
>> if (is_base_of(typeid(T),a.type)) return some_cast<T*>(a.ptr);
>> else return nullptr;
>> }
>>
>> There are a couple of issues with that!
>>
>> 1. We need a run-time is_base_of, which doesn't currently exist.
>>
>> 2. There isn't a suitable cast from void* to the base type. (In
>> particular, it's
>> clear that in the case of multiple inheritance we will need to apply an
>> offset to
>> get the correct base class.)
>>
>> Questions for the experts:
>>
>> * Am I right that this is not possible with the current core language?
>>
>> * I have the feeling that the run-time information needed to implement
>> the missing is_base_of and cast needed here may already exist to
>> support language features like member pointers and exceptions. Is
>> that true?
>>
>
> Yes, I think we discussed this recently, possibly on this list. It's
> certainly possible on Itanium. Obviously it wouldn't work for
> implementations that have been made noncompliant by disabling rtti and /or
> exceptions.


Of course it only took an hour after writing the email to work out
how to do this...

The key thing is that we can throw a Derived* and catch a Base*.

So any can store a function that throws the contained value, and the
any_cast implementation can invoke that function, and catch the cast-to
type.

Would an implementation that didn't throw-catch but used the
compiler-generated tables more directly be significantly more efficient?

Here is my freshly-written and barely-tested implementation. Comments
appreciated!


class Any
{
  void* ptr;
  const std::type_info* type_p; // Only needed for type(), hmm.
  std::function< void (Any*) > delete_func;
  std::function< void*(const Any*) > clone_func;
  std::function< void (Any*) > throw_func;

public:
  /*constexpr*/ Any() noexcept: // Not constexpr because std::function() isn't.
    ptr {nullptr}
  {}

  Any(const Any& other):
    ptr {other.ptr ? other.clone_func(&other) : nullptr},
    type_p {other.type_p},
    delete_func {other.delete_func},
    clone_func {other.clone_func},
    throw_func {other.throw_func}
  {}

  Any(Any&& other):
    ptr {std::exchange(other.ptr,nullptr)},
    type_p {other.type_p},
    delete_func {other.delete_func},
    clone_func {other.clone_func},
    throw_func {other.throw_func}
  {}

  template <typename T>
  Any(T&& val):
    ptr{ new std::decay_t<T>{ std::forward<T>(val) } },
    type_p{ &typeid(std::decay_t<T>) },
    delete_func{ [](Any* this_) {
      auto p = static_cast<std::decay_t<T>*>(this_->ptr);
      delete p;
    } },
    clone_func{ [](const Any* this_) {
      auto p = static_cast<std::decay_t<T>*>(this_->ptr);
      return new std::decay_t<T>{ *p };
    } },
    throw_func{ [](Any* this_) {
      auto p = static_cast<std::decay_t<T>*>(this_->ptr);
      throw p;
    } }
  {}

  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 T>
  Any& operator=(T&& rhs)
  {
    Any{ std::forward<T>(rhs) } . swap(*this);
    return *this;
  }

  ~Any()
  {
    if (ptr) delete_func(this);
  }

  void reset()
  {
    Any{} . swap(*this);
  }

  void swap(Any& other) noexcept
  {
    using std::swap;
    swap(ptr, other.ptr);
    swap(delete_func, other.delete_func);
    swap(clone_func, other.clone_func);
    swap(throw_func, other.throw_func);
  }

  bool has_value() const noexcept
  {
    return ptr;
  }

  const std::type_info& type() const noexcept
  {
    return *type_p;
  }

  template <typename U>
  friend U* Any_cast(Any* a)
  {
    if (!(a->ptr)) return nullptr;
    try {
      a->throw_func(a);
      __builtin_unreachable();
    }
    catch (U* p) {
      return p;
    }
    catch (...) {
      return nullptr;
    }
  }
};


void swap(Any& lhs, Any& rhs) noexcept
{
  lhs.swap(rhs);
}

Received on 2023-03-26 19:11:30