gracicot / kangaru

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

Inconsistent behaviour for move-only types when using MSVC. #113

Closed ClawmanCat closed 6 months ago

ClawmanCat commented 1 year ago

Describe the bug Using move-only types (deleted copy constructor/assignment operator) as services produces compile errors in various situations with MSVC where it does not with Clang. For example:

To Reproduce The following code will compile without any issues with Clang but fail to compile when using MSVC:

#include <kangaru/kangaru.hpp>

struct service_1 {
    friend auto service_map(const service_1&) -> kgr::autowire_single;

    // Movable & Copyable
    service_1(void)  = default;
    ~service_1(void) = default;
    service_1(const service_1&) = default;
    service_1& operator=(const service_1&) = default;
    service_1(service_1&&) = default;
    service_1& operator=(service_1&&) = default;

    int method(void) { return 1; }
};

struct service_2 {
    friend auto service_map(const service_2&) -> kgr::autowire_single;

    // Only Movable.
    service_2(void)  = default;
    ~service_2(void) = default;
    service_2(const service_2&) = delete;
    service_2& operator=(const service_2&) = delete;
    service_2(service_2&&) = default;
    service_2& operator=(service_2&&) = default;

    int method(void) { return 2; }
};

int main() {
    kgr::container services;

    // Works with both MSVC and Clang.
    auto result_1 = services.invoke([] (service_1& service) { return service.method(); });

    // Works with Clang but not MSVC.
    auto result_2 = services.invoke([] (service_2& service) { return service.method(); });
}   

MSVC will fail to compile with the following error:

...\tests\kangaru.cpp(41): error C2280: 'kgr::detail::sink kgr::container::invoke<>(kgr::detail::not_invokable_error,...)': attempting to reference a deleted function
...\include\kangaru\container.hpp(187): note: see declaration of 'kgr::container::invoke'

Given the same struct definitions as above, the following also compiles with Clang but not MSVC:

struct service_3 {
    friend auto service_map(const service_3&) -> kgr::autowire_single;
    explicit service_3(service_2& dependency) {}
};

int main() {
    kgr::container services;

    // Works with both MSVC and Clang.
    auto& s2 = services.service<kgr::mapped_service_t<service_2>>();

    // Works with Clang but not MSVC.
    auto& s3 = services.service<kgr::mapped_service_t<service_3>>();
}

MSVC gives the following error:

...\tests\kangaru.cpp(48): error C2280: 'kgr::detail::sink kgr::container::service<kgr::single_service<service_3,kgr::detail::autowire_map<kgr::service,kgr::detail::decay_t,kgr::map<>,8>>,0>(kgr::detail::service_error<kgr::single_service<service_3,kgr::detail::autowire_map<kgr::service,kgr::detail::decay_t,kgr::map<>,8>>>)': attempting to reference a deleted function
...\include\kangaru\container.hpp(135): note: see declaration of 'kgr::container::service'
...\tests\kangaru.cpp(48): error C2440: 'initializing': cannot convert from 'kgr::detail::sink' to 'kgr::detail::sink &'
...\tests\kangaru.cpp(48): note: A non-const reference may only be bound to an lvalue

Expected behavior I did not see any limitations against move-only service types on the wiki, so I would expect it to compile. Either way, the behaviour should be consistent between compilers. kgr::debug::service<service_2>() does also not list any issues, which I would expect if this was not allowed.

Desktop (please complete the following information):

Additional context I also tried using a normal service definition instead of autowiring, the error is the same.

gracicot commented 1 year ago

Thanks for the report, I'll take a look as soon as possible. I should have access to a machine with MSVC again and I'll see what's gone wrong.

gracicot commented 6 months ago

I can confirm the problem, but all workaround I found only works with very recent MSVC versions so I cannot apply a fix. All other MSVC version will crash with an internal compiler error.

An imperfect workaround would be to map the type only for non const references:

struct service_2 {
    friend auto service_map(service_2&) -> kgr::autowire_single;

    // ...
};

The service map in kangaru 4 is not precise enough to allow mapping other types reference type in addition without quickly running into issues.