Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service map autowire with polymorphic services #111

Open
JeWe37 opened this issue Apr 28, 2023 · 8 comments
Open

Service map autowire with polymorphic services #111

JeWe37 opened this issue Apr 28, 2023 · 8 comments

Comments

@JeWe37
Copy link

JeWe37 commented Apr 28, 2023

Is your feature request related to a problem? Please describe.
I'm trying to auto wire polymorphic services in the simplest way possible.

Describe the solution you'd like
I'd like to be able to do this through the service map.

Describe alternatives you've considered
So far this can only be done with services that you define yourself and then use autowire to detect the dependencies.

Additional context
This should by all accounts be as simple as just adding a few more definitions and aliases, inheriting from both the four existing services and kgr::polymorphic in autowire.hpp. Might give this a shot myself in a PR. Given that I don't have an overview of this I can't say if I may be missing something though.

@JeWe37
Copy link
Author

JeWe37 commented Apr 28, 2023

Another nice option might be too let users pass a list of types as variadic template parameters to the new alias. This would allow you to not have as many aliases while also allowing for autowiring of supplied services and similar functionality that is just implemented as simple tags.

@JeWe37
Copy link
Author

JeWe37 commented Apr 28, 2023

So I took a closer look at this. The latter idea is somewhat hard to implement due to it requiring that information to be passed pretty much through the whole autowire stack. Creating the corresponding kgr::override classes is also really not that easy with autowiring, as those would also require passing another template parameter. Could perhaps be a worthwhile thought for 5.x.y?

@gracicot
Copy link
Owner

gracicot commented Apr 28, 2023

Hi! Yeah, polymorphic services has not been added to the default autowire mapper. Probably an oversight on my part since I'm not using them much. An autowire mapper that supports it might be implemented like so:

template<typename... Overrides>
struct autowire_polymorphic {
    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>, kgr::autowire>;
};

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

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

I haven't tried since I'm on mobile, but you should be able to use is like this:

struct my_type_abstract {
    virtual ~my_type_abstract() = default;
    virtual void foo() = 0;

    friend auto service_map(my_type_abstract const&) -> autowire_abstract;
};

struct my_type_override : my_type_abstract {
    void foo() override {}

    friend auto service_map(my_type_override const&) -> autowire_polymorphic<my_type_abstract>;
};

It works because the service_map function must return either a service type, or a type that contains a template alias named mapped_service so a service type can be generated.

Let me know if it helps!

@JeWe37
Copy link
Author

JeWe37 commented Apr 28, 2023

Doesn't quite seem to work. Fixing some minor details I ended up with

template<typename... Overrides>
struct autowire_polymorphic {
    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>>;
};

However this complains

test.cpp:3029:59: error: use of deleted function ‘kgr::detail::sink kgr::container::service(kgr::detail::service_error<T>) [with T = autowire_polymorphic<Camera>::Service<OrthogonalCamera>; typename std::enable_if<std::is_default_constructible<kgr::detail::service_error<T> >::value, int>::type <anonymous> = 0]’
 3029 |                 container.service<OrthogonalCameraService>();
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
test.cpp:1749:7: note: declared here
 1749 |  auto service(detail::service_error<T> = {}) -> detail::sink = delete;
      |       ^~~~~~~

and frankly I don't know enough about the internals of kangaru to diagnose this. With abstract I get bigger problems yet, as it does not seem to be registering properly inside the autowiring and isn't even found by kgr::mapped_service_t<T>?

@JeWe37
Copy link
Author

JeWe37 commented Apr 28, 2023

Update: After a bit of thinking about this for myself, I tried it with actually having two separate indirect maps for the polymorphic and override classes. That worked:

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

(The docs of this project are honestly excellent-despite the complexity of the template hell that it is) Still I don't know how to make abstract work-but I don't need that anyway for my case. I'll leave this open though as it seems you agree this should be a standard component.

@gracicot
Copy link
Owner

gracicot commented Apr 30, 2023

Hey! Sorry for the delay. I modified example 3 to work with autowire. Here's the full example, with abstract services working.

The docs of this project are honestly excellent-despite the complexity of the template hell that it is

Thank you! I'm trying my best, both to make the doc better and to make it less of a template hell. Feedback like yours is really helpful getting the small details.

To make abstract service working, you still need to define at runtime which service the container will be using. This is done through overrides.

Anyway, here's the code. Let me know if it works for you! I'm looking forward making this a builtin feature.

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

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 void projection() = 0;
    friend auto service_map(AbstractCamera const&) -> autowire_abstract;
};

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

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

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

int main()
{
    auto c = kgr::container{};
    
    c.emplace<kgr::mapped_service_t<Camera>>();
    
    c.invoke([](AbstractCamera& c) {
        c.projection();
    });
    
    c.emplace<kgr::mapped_service_t<OrthogonalCamera>>();
    
    c.invoke([](Camera& c) {
        c.projection();
    });
}

Output:

default projection
orthogonal projection

@wopss
Copy link
Contributor

wopss commented Feb 10, 2024

@gracicot any news on this? It would be really nice to have this in.

@gracicot
Copy link
Owner

@wopss I will release v4.4.0 for this, it's quite easy to add. PR appreciated, but I'll probably find some time in the coming months

@gracicot gracicot modified the milestones: 4.3.2, 4.4.0 Nov 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants