c++c++17stdstdany

Upcasting through std::any


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::anys 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;
}

Solution

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