gracicot / kangaru

🦘 A dependency injection container for C++11, C++14 and later
MIT License
494 stars 39 forks source link

Polymorphic service issue #118

Closed rafjmac closed 6 months ago

rafjmac commented 10 months ago

Hi,

I am currently investigating possibilities of the kangaru and I have a problem defining a polymorphic service which shall not be a single one:

struct Camera {
    virtual void projection() = 0;
};

struct PerspectiveCamera : Camera {
    void projection() override {
        std::cout << "perspective projection" << std::endl;
    }
};

struct CameraService : kgr::abstract_service<Camera>;
struct PerspectiveCameraService : kgr::service<PerspectiveCamera>, kgr::overrides<CameraService> {};

kgr::container container;
container.emplace<PerspectiveCameraService>();    
Camera& camera = container.service<CameraService>();

Error that pops up is about deleted function emplace<PerspectiveCameraService>(). What I am trying to get is that there is some interface class but in case anyone wants to use it, it must get a unique instance. I haven't gone through details of the library, but I hope you can answer whether it is possible but I am doing something wrong or support for such case was not planned.

gracicot commented 10 months ago

We don't have first class support for non single polymorphic service, but you can definitely roll up your own service kind that does just that.

You're not the first one to ask for such feature, but I haven't find a way to implement a generic solution for this just yet.

kgr::service will always return by value, and a polymorphic override requires the service to be single. The library detects such conflict and refuse your service definition, hence your error of a deleted function.

To fix this you can create a custom service definition that contains a factory method that calls the container for a new instance, and return by std::unique_ptr. The interface is a bit hard to understand in kangaru 4, but not impossible to do. We have a documentation page for custom service definition and an example.

Let me know if you need additional support.

rafjmac commented 10 months ago

Hi,

Thanks for the explanation. Also, I have one additional question - does the autowire feature work with interfaces? I read issue https://github.com/gracicot/kangaru/issues/111, tried to compile and it worked correctly. However, I tried to add another class that uses AbstractCamera& but it was not compiling.

rafjmac commented 10 months ago

I investigated the issue I had and the problem was that I added a virtual destructor - so apparently no problem with inheritance, etc. but the virtual destructor made my code to not compile. Here is the example if you want to try yourself:

#include <iostream>
#include <kangaru/kangaru.hpp>

struct autowire_abstract
{
    template <typename T> struct Service : kgr::abstract_service<T>
    {
    };

    template <typename T> using mapped_service = Service<std::decay_t<T>>;
};

template <typename... Overrides> struct autowire_override
{
    template <typename T>
    struct Service : kgr::single_service<T, kgr::autowire>, kgr::overrides<kgr::mapped_service_t<Overrides>...>
    {
    };

    template <typename T> using mapped_service = Service<std::decay_t<T>>;
};

struct autowire_polymorphic
{
    template <typename T> struct Service : kgr::single_service<T, kgr::autowire>, kgr::polymorphic
    {
    };

    template <typename T> using mapped_service = Service<std::decay_t<T>>;
};

struct AbstractCamera
{
    virtual ~AbstractCamera() = default;
    virtual void projection() = 0;
    friend auto service_map(AbstractCamera const&) -> autowire_abstract;
};

struct Camera : AbstractCamera
{
    ~Camera() = default;
    void projection() override
    {
        std::cout << "default projection" << std::endl;
    }
    friend auto service_map(Camera const&) -> autowire_override<AbstractCamera>;
};

int main()
{
    auto c = kgr::container{};

    c.emplace<kgr::mapped_service_t<Camera>>();
    c.invoke([](AbstractCamera& c) { c.projection(); });
}
gracicot commented 10 months ago

What compiler do you use? With GCC 13 this code compiles correctly and runs. I got the output default projection

rafjmac commented 10 months ago

Ah okay, I haven't checked any other one. I use Visual Studio 17 2022. Then it seems like a bug in MSVC.

rafjmac commented 10 months ago

Checked with Clang 17.0.6 on windows and it works.

gracicot commented 9 months ago

Let me see what I can do to help MSVC with this one

gracicot commented 6 months ago

There's a simple workaround for this problem. Simply probe the service map using const ref types:

template <typename... Overrides> struct autowire_override
{
    template <typename T>
    struct Service : kgr::single_service<T, kgr::autowire>, kgr::overrides<kgr::mapped_service_t<Overrides const&>...>
    {
    };

    template <typename T> using mapped_service = Service<std::decay_t<T>>;
};

The root cause seems similar to issue #113 which means little can be done on my side to fix this issue without major refactoring of how the service map works, and modern technique that would make it possible are simply not available in the compilers kangaru 4 is set to support.

Since there is an easy workaround I will close this issue. Let me know if despite the workaround you still experience difficulties with autowire on msvc.