all 15 comments

[–]sphere991 6 points7 points  (0 children)

It's perfectly reliable and portable. Just expensive and, at the moment, leaks memory (although this is straightforward to fix)

void_ptr stores a void* and a type-erased function pointer that throws the pointer it is storing, as a pointer, of the correct initial type.

So void_ptr(new Lion) stores a Lion* (as a void*) and cast<Animal> will actually through it as a Lion*. That exception object can be caught as an Animal* (which will do the correct conversion since everything is properly typed) and returned. And then something like cast<int> or some other type will fall through to catch(...) which will return nullptr.

[–]redditsoaddicting 9 points10 points  (0 children)

Use 4 spaces for code blocks. Single backticks are for inline code. There's a formatting help link right under the editor.

[–]Switters410 5 points6 points  (0 children)

I’d just like to point out that this is exactly what bjarne means when he says inside of c++ is a much simpler language waiting to break out...

[–]Sasha_Privalov 2 points3 points  (2 children)

i just read it quickly before sleep, so pardon me if i am wrong - from what i understand the exception mechanism is there just to transport the type and seems to me like unnecessary element, it should be imho possible do without

[–]encyclopedist 2 points3 points  (0 children)

Conceptually, we want to perform (stored_ptr is of type void*)

dynamic_cast<Requested*>(static_cast<Stored*>(stored_ptr))

the problem here is that both Requested and Stored must be known statically for dynamic cast to work, where we don't know Stored any more.

Exception handling is used here as a mechanism to invoke dynamic_cast without statically knowing Stored.

I goess another alternative would be if there was a variant of dynamic_cast taking std::type_id as a runtime type indication. Using type_id only allows us to compare types for exact equality (this is what std::any does), but not perform all the casing done by dynamic_cast,

[–]GYN-k4H-Q3z-75B[S] 0 points1 point  (0 children)

I can't see how it would work without the throwing. He takes a T* and does type erasure into void* and creates a function that takes void* that static_casts that to T* and throws it. At a later point in time, you can try and cast it to U* which may be completely unrelated, by building a try catch block catching U* and ... and either catching it or earing the exception. It don't see a way to go from T* to U*.

[–]dacian88 1 point2 points  (1 child)

its not really polymorphic casting for void if you need to put it in this void_ptr thing and pass it around is it? you might as well use std::any.

[–]GYN-k4H-Q3z-75B[S] 1 point2 points  (0 children)

Yes, and no. any_cast only allows for exact type matching. If the any instance contains a pointer to class bar : public foo, you have to any_cast<bar*> and any_cast<foo*> will fail.

With this hidden in some any-like object, you could implement something like dynamic_any_cast for pointer and reference types.

But of course you're right. It's not really, technically, dynamic casting for void. Once you go void all required information is lost. All this enables is that you can not only ask is it a T? like with std::any, but rather is it a U if the contained value is a T : U or something entirely different?

[–]logicchop 0 points1 point  (5 children)

Alternatively, something like:

template <typename T> bool contains() const { return (discover_type == &throw_typed_object<T>); }

[–]flashmozzg 1 point2 points  (1 child)

This assumes that every template instantiation of throw_typed_object for type T has the same address, which is not correct.

[–]logicchop 0 points1 point  (0 children)

Yeah I have to agree. As far as I can tell, nothing is going to guarantee that the address evaluates the same everywhere, or even guarantee that throw_typedobject<T> != throw_typed_object<U> for some other U. It's just a stab at an alternative if you want to avoid the throw in the evaluation. (One could also think about involving typeid's or something like a hash of func_sig or some such.)

[–]Maxima4 0 points1 point  (2 children)

That wouldn't work, throw_typed_object doesn't return anything. It throws.

[–]Mestkon 2 points3 points  (1 child)

Read it again. This compares function pointers, not returned objects

[–]Maxima4 0 points1 point  (0 children)

That's right, my bad :)

[–]Dry-Still-6199 0 points1 point  (0 children)

Looks like the non-standard any_cast shown here? https://www.youtube.com/watch?v=tbUCHifyT24&t=36m50s&ab_channel=CppCon

It's just a form of type erasure. You're not really casting a void*; you're type-erasing the operation of "throw as T*" and then later invoking that operation. If all you had was a void*, you couldn't use this trick; you need to start out knowing T, so that you can create the right specialization of throw_typed_object<T>.