In C++ you can pass instances of Derived
to functions accepting Base
, if Base
is a base class of Derived
. This is very useful for satisfying APIs and a very common design pattern for APIs.
Currently I am faced with a situation, where I want to upcast through an std::any
. That is I have an std::any
that stores an instance of Derived
and I would like to cast its address to a pointer to Base
in an API function, that should not know of the existence of Derived
. My use case is a runtime reflection library that passes around std::any
s storing instances of the reflected types.
I understand that upcasting through std::any
is not possible, because std::any_cast
checks the typeid
before casting and returns a nullptr
if the types don't match, see cppreference.
But maybe there is a workaround or some clever trick that I could use? It feels like it must be possible somehow, as upcasting is such a common thing in C++ and the std::any
has the address and type I need.
Here is a code example. The following code segfaults as expected because the std::any_cast
returns a nullptr
in any_function
which is dereferenced in the following line. Compiler explorer: https://godbolt.org/z/E9sG9G3ff
#include <any>
#include <iostream>
class Base
{
public:
double get_val() const {
return val;
}
private:
double val {1.23};
};
void normal_function(Base const& b){
std::cout << b.get_val() << std::endl;
};
void any_function(std::any const& b){
auto* ptr = std::any_cast<Base>(&b);
std::cout << ptr->get_val() << std::endl;
}
class Derived : public Base {};
int main()
{
Derived d;
// normal upcasting
normal_function(d);
// upcasting thru std::any
std::any ad = d;
any_function(ad);
return 0;
}
any
is a type-safe void*
. That means it obeys the rules of a void*
. Namely, if you cast a T*
to a void*
, the only type you can cast it back to is T*
. Not to a U*
where U
is a base class of T
. It must be exactly and only T*
.
The same goes for any
.
any
(like this use of void*
) is for situations where code A needs to communicate with code C, through some intermediary B. A and C agree on what type is involved, but B doesn't need to know what type is being communicated (perhaps because B facilitates transactions between many different pairs of As and Cs).
Agreeing on what type is involved means the exact type that is involved. If A is communicating with code that only takes a base class, then that's what it needs to provide (by storing a pointer to the base class of its derived class instance).
Now, the general idea of what you're asking for is not unreasonable. However, implementing it is... difficult. Most forms of type erasure can handle types that have similar behavior, but the behavior of the type being erased needs to be known at the time of the erasure, not the time of use of that functionality. If a type needs to be convertible to some type, then exactly what that type is needs to be known when the object is added to the any
. That would become part of the interface of the hypothetical any
type, and thus it would be baked into its code.
It cannot take "any" type and then later ask, "hey, are you convertible to ThisType
?"