On Sun, 26 Mar 2023, 15:11 Phil Endecott, <std_proposals_list@chezphil.org> wrote:
Edward Catmur wrote:
> On Sun, 26 Mar 2023, 13:29 Phil Endecott via Std-Proposals, <
> std-proposals@lists.isocpp.org> 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?

Almost certainly; you'd still be paying a virtual function call and a data structure traversal in the case of multiple inheritance, but you'd save on exception allocation and (most pertinently) unwinding. Why not measure the difference? The Itanium abi function is std::type_info::__do_catch, iirc. 


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);
}