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