[type_erasure] Downcasting is now possible
AMDG
I've been asked about a function for downcasting
an any several times, and I finally implemented it.
Short story: you can now use dynamic_any_cast to
convert between two arbitrary any types.
Here's a quick example which tries to cast a
boost::any equivalent to a boost::function
equivalent.
using plain_any = any
On Sat, Mar 7, 2015 at 8:27 PM, Steven Watanabe
The calls to register_binding are necessary for this to work, unfortunately. I don't think it's possible to implement dynamic_any_cast without some kind of registration, but at least it only needs to be called once for each concept/type combination. If anyone has any better ideas about how to handle this, I'd love to hear it.
Comments, questions, and criticism are welcome.
Awesome. I haven't looked at the implementation and haven't thought about the problem enough, so forgive me if this is a silly question -- why is it that register_binding needs to be invoked *manually*? In other words, why can't the instantiation of dynamic_any_cast do something like examine the types of all of the concepts involved, then force an instantiation of a template for each of those concept types, which would perform the registration? To be clear, the purpose of this instantiation would be to create a type that has a static member with a constructor that performs the registration. You'd just need to then "touch" the object to make sure the constructor actually gets run. Since the registration is done via an instantiation of a template that uses the concept as an argument, that registration is guaranteed to only happen exactly once per concept, regardless of how many times the dynamic_any_cast is used across translation units. -- -Matt Calabrese
AMDG On 03/09/2015 03:04 PM, Matt Calabrese wrote:
Awesome. I haven't looked at the implementation and haven't thought about the problem enough, so forgive me if this is a silly question -- why is it that register_binding needs to be invoked *manually*? In other words, why can't the instantiation of dynamic_any_cast do something like examine the types of all of the concepts involved, then force an instantiation of a template for each of those concept types, which would perform the registration?
dynamic_any_cast cannot do this registration because
it only knows the source and destination concepts.
It knows the contained type at *runtime* via
std::type_info, which isn't suitable for template
instantiations. If it were able to do such registration,
then no registration would be necessary in the first
place, since that would mean that dynamic_any_cast
knows everything it needs statically.
The only place where such an instantiation could
happen is in the constructor of any, since the
constructor is the boundary where the type is erased.
I chose not to do this for two reasons:
1) You don't pay for what you don't use. Putting
the registration in the constructor means that
it will always happen whether it's needed or not.
2) It still won't catch everything. In the example
I posted, the object is captured by an any type
which does not support callable, and later cast
to another any type which does. The constructor
will therefore not instantiate callable
To be clear, the purpose of this instantiation would be to create a type that has a static member with a constructor that performs the registration. You'd just need to then "touch" the object to make sure the constructor actually gets run. Since the registration is done via an instantiation of a template that uses the concept as an argument, that registration is guaranteed to only happen exactly once per concept, regardless of how many times the dynamic_any_cast is used across translation units.
In Christ, Steven Watanabe
On Mon, Mar 9, 2015 at 4:29 PM, Steven Watanabe
AMDG
On 03/09/2015 03:04 PM, Matt Calabrese wrote:
Awesome. I haven't looked at the implementation and haven't thought about the problem enough, so forgive me if this is a silly question -- why is
it
that register_binding needs to be invoked *manually*? In other words, why can't the instantiation of dynamic_any_cast do something like examine the types of all of the concepts involved, then force an instantiation of a template for each of those concept types, which would perform the registration?
dynamic_any_cast cannot do this registration because it only knows the source and destination concepts. It knows the contained type at *runtime* via std::type_info, which isn't suitable for template instantiations.
Ah, I didn't notice the void(*)() part in the example binding (which is the target type). I think I understand a bit better now. When I first looked at the example I though you just needed to register the concepts for some reason. 1) You don't pay for what you don't use. Putting
the registration in the constructor means that it will always happen whether it's needed or not. 2) It still won't catch everything. In the example I posted, the object is captured by an any type which does not support callable, and later cast to another any type which does. The constructor will therefore not instantiate callable
. I think that explicit manual registration is less likely to to cause hard to find bugs that automatic registration that only works most of the time.
Okay, this facility is much different from what I thought it was. I should
have looked at the example code more closely before replying. I read the
description and thought it was for the following type of situation:
//////////
void foo() {}
function_any
AMDG On 03/09/2015 06:36 PM, Matt Calabrese wrote:
On Mon, Mar 9, 2015 at 4:29 PM, Steven Watanabe
wrote: 1) You don't pay for what you don't use. Putting the registration in the constructor means that it will always happen whether it's needed or not. 2) It still won't catch everything. In the example I posted, the object is captured by an any type which does not support callable, and later cast to another any type which does. The constructor will therefore not instantiate callable
. I think that explicit manual registration is less likely to to cause hard to find bugs that automatic registration that only works most of the time. Okay, this facility is much different from what I thought it was. I should have looked at the example code more closely before replying. I read the description and thought it was for the following type of situation:
////////// void foo() {} function_any
fun_foo = &foo; // Start by type-erasing via a callable concept // Convert to a less-refined concept plain_any any_foo = fun_foo; // target type is still void(*)(), no additional indirection
// Get the object back as a function_any
dynamic_any_cast >(any_foo); // should succeed ////////// In other words, I assumed it only worked if the type were originally erased via the more-refined concept before being converted to the less-refined any. In this case I imagine you would be able to keep track of the information by way of the original conversion.
From an implementation stand-point, just casting
to the original captured type is pretty easy to
support. The problem is that if I support that,
then several other kinds of casts should be possible
as well.
If we have an any, we should be able to do
an upcast followed by a downcast, regardless
of where the any came from. Thus, the following
sequence should work:
- capture as RandomAccessIterator
- cast to BidirectionalIterator
- cast to ForwardIterator
- dynamic_any_cast back to BidirectionalIterator
The cast sequence RandomAccessIterator -> BidirectionalIterator ->
ForwardIterator should be exactly equivalent to
RandomAccessIterator -> ForwardIterator. Therefore,
the following should also work:
- capture as RandomAccessIterator
- cast to ForwardIterator
- dynamic_any_cast to BidirectionalIterator
The simplest rule to make sure that such conversions
always work is to say that if an object was originally
captured as ConceptA, then a dynamic_any_cast from
ConceptB to ConceptC will work if ConceptA is more refined
than ConceptC.
So far it's all pretty straightforward. Now we come to
the annoying part. I think that every any which holds the
same value should be equivalent. In other words, if I have
any<...> a1, a2; and typeid_of(a1) == typeid_of(a2), then
dynamic_any_cast
On 08/03/2015 05:27, Steven Watanabe wrote:
The calls to register_binding are necessary for this to work, unfortunately. I don't think it's possible to implement dynamic_any_cast without some kind of registration, but at least it only needs to be called once for each concept/type combination. If anyone has any better ideas about how to handle this, I'd love to hear it.
If you're doing this, you might as well implement full dynamic duck typing. If the expression is valid, you call it, otherwise you raise an exception.
AMDG On 03/10/2015 07:33 AM, Mathias Gaunard wrote:
On 08/03/2015 05:27, Steven Watanabe wrote:
The calls to register_binding are necessary for this to work, unfortunately. I don't think it's possible to implement dynamic_any_cast without some kind of registration, but at least it only needs to be called once for each concept/type combination. If anyone has any better ideas about how to handle this, I'd love to hear it.
If you're doing this, you might as well implement full dynamic duck typing. If the expression is valid, you call it, otherwise you raise an exception.
Oh, I agree. However, the cost of such a call is significantly greater then a normal virtual call. Thus, I don't want the library to do this without being told explicitly that it's the desired behavior. With what I have already, the implementation of such a feature is trivial. The real issue is defining the interface for it. In Christ, Steven Watanabe
On 10/03/2015 17:38, Steven Watanabe wrote:
AMDG
On 03/10/2015 07:33 AM, Mathias Gaunard wrote:
On 08/03/2015 05:27, Steven Watanabe wrote:
The calls to register_binding are necessary for this to work, unfortunately. I don't think it's possible to implement dynamic_any_cast without some kind of registration, but at least it only needs to be called once for each concept/type combination. If anyone has any better ideas about how to handle this, I'd love to hear it.
If you're doing this, you might as well implement full dynamic duck typing. If the expression is valid, you call it, otherwise you raise an exception.
Oh, I agree. However, the cost of such a call is significantly greater then a normal virtual call. Thus, I don't want the library to do this without being told explicitly that it's the desired behavior. With what I have already, the implementation of such a feature is trivial. The real issue is defining the interface for it.
How about something like this: any a; duck_visit(f, a); with 'f' a polymorphic function object constrained with SFINAE. if f(a) is callable for the dynamic type of a, then call it, otherwise raise an exception.
AMDG On 03/10/2015 11:24 AM, Mathias Gaunard wrote:
How about something like this:
any a; duck_visit(f, a);
with 'f' a polymorphic function object constrained with SFINAE.
This isn't quite going to work be itself. SFINAE
doesn't actually matter as there's no way to make
duck_visit work without calling register_
if f(a) is callable for the dynamic type of a, then call it, otherwise raise an exception.
It's a start. Of course, there's no reason to limit it to one argument: any a, b, c; duck_visit(f, a, b, c); As a matter of fact, I already have a function named call which looks a lot like this, except that it dispatches using the arguments' vtables. The only real difficulty is how to integrate it into my existing framework. In Boost.TypeErasure, any is a class template. Handling the arguments isn't a problem, but what is the return type of duck_visit? In Christ, Steven Watanabe
participants (3)
-
Mathias Gaunard
-
Matt Calabrese
-
Steven Watanabe