C++ Logo

std-proposals

Advanced search

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

From: Edward Catmur <ecatmur_at_[hidden]>
Date: Sun, 26 Mar 2023 15:31:57 -0400
On Sun, 26 Mar 2023, 15:11 Phil Endecott, <std_proposals_list_at_[hidden]>
wrote:

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

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

Received on 2023-03-26 19:32:12