How do I turn shared_ptr<void> into another type?

Advertisements

I’m trying to build an injection class that allows any other class to be injected under a name, for the purpose of automatically passing said injection class to the injected class’s constructor. The reasoning for doing this is because the injection class contains some variables that are needed to set up the injected class that I’d like to have managed.

Currently I have some code like this:

#include <memory>
#include <string>
#include <stdout>
#include <typeindex>

class Injector {
private:
    std::map<std::type_index, std::map<std::string, std::shared_ptr<void>>> instances;

public:
    template<class T, typename... Args>
    void inject(std::string name, Args&&... args) {
        static_assert(std::is_constructible_v<T, Args..., Injector*>, "Cannot construct type with given args");
        auto resourcePtr = std::make_shared<T>(std::forward<Args>(args)..., this);
        instances[typeid(T)][name] = resourcePtr;
    }

    template<class T>
    T get(std::string name) {
        auto nameToTMap = instances.find(typeid(T));
        if (nameToTMap == instances.end()) {
            return std::nullopt;
        }

        auto mapInstance = nameToTMap->second;
        auto instance = mapInstance.find(name);
        if (instance == mapInstance.end()) {
            return std::nullopt;
        }

        // Here is my problem
        auto typedInstance = std::dynamic_pointer_cast<T>(instance->second);
        if (!typedInstance) {
           return std::nullopt;
        }

        return typedInstance;
    }
};

class Foo {
public:
    Foo(int x, int y, Injector injector) {
       printf("Hello from Foo, %i, %i", x, y);
    }
}

int main() {
    Injector injector;
    
    injector.inject<Foo>("foo", 1, 2); // Prints "Hello from Foo, 1, 2"
    std::shared_ptr<Foo> myFoo = injector.get<Foo>("foo");

    return 0;
}

When compiling (clang++) I get the error:

error: 'void' is not a class type
    _ET* __p = dynamic_cast<_ET*>(__r.get());
               ^                  ~~~~~~~~~
note: in instantiation of function template specialization 'std::__1::dynamic_pointer_cast<Foo, void>' requested here
                auto typedInstance = std::dynamic_pointer_cast<T>(instance->second);

I’ve tried several different things (like having an Injectable class) but nothing works so I’m wondering if something like this even works in C++? I know Casting from shared_ptr<void> to shared_ptr<T> will always have the correct result because the void ptr is stored in a map under the key of T

If this won’t work are there any other ways I can automatically pass a variable to instantiations / function calls in a reliable way?

>Solution :

.get() doesn’t belong there. std::dynamic_pointer_cast applies to a shared_ptr directly, not a raw pointer.

Also, you want std::static_pointer_cast instead. That’s equivalent to static_cast being the correct choice if you were to use raw pointers instead of std::shared_ptr. dynamic_cast casts between pointers to different (sub)objects in a polymorphic class hierarchy. static_cast casts from void* to any other pointer type without affecting to which object the pointer points (assuming the pointer’s address is suitably aligned for T).

You are responsible to assure that the std::shared_ptr<void> actually points to an object of type T. Otherwise the cast with std::static_pointer_cast<T> will (with very few exceptions) result in undefined behavior when the resulting pointer is used.

In particular if (!typedInstance) doesn’t do anything useful in regards to the cast. It can only fail if the original std::shared_ptr<void> was already empty to begin with. If the std::shared_ptr<void> doesn’t point to an object of type T, then the result of the cast will still not be (guaranteed to be) a null pointer.

Leave a Reply Cancel reply