Search code examples
c++c++17variadic-templates

C++ - Function with multiple parameter packs and a std::function as argument


I am trying to create an IoC Container in C++ that resolves dependencies automatically.

For that I created a function with two variadic parameter packs that is declared like this:

template <class T, typename ... TDependencies, typename... TArgs> 
void Register(std::function<std::shared_ptr<T> (std::shared_ptr<TDependencies> ...,
TArgs ...)> && pFactory)

Apparently, it seems the compiler is unable to match this when supplied with

Register<Foo, Bar>(std::function<std::shared_ptr<Foo>(std::shared_ptr<Bar>)>(
[](std::shared_ptr<Bar> bar){return std::make_shared<Foo>(bar);}));

The compile errors say

note: candidate: 'void Container::Register(std::function<std::shared_ptr<_Tp>
(std::shared_ptr<TDependencies>..., TArgs ...)>&&)
[with T = Foo; TDependencies = {Bar}; TArgs = {std::shared_ptr<Bar>}]'

Apparently, it matches std::shared_ptr<Bar> twice. How can I get the compiler not to match the shared_ptr in TArgs too?


Solution

  • Rather than trying to deduce TDependencies directly from the pFactory parameter type, I'd write a type trait to get the dependencies from the whole parameter pack instead. With boost::mp11:

    template <class>
    struct is_shared_ptr : std::false_type {};
    
    template <class T>
    struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};
    
    namespace mp11 = ::boost::mp11;
    
    template <class... Ts>
    using register_traits = mp11::mp_partition<mp11::mp_list<Ts...>, is_shared_ptr>;
    
    template <class T, class F, class... TDependencies, class... TArgs>
    void RegisterImpl(F && pFactory,
                      mp11::mp_list<
                          mp11::mp_list<std::shared_ptr<TDependencies>...>,
                          mp11::mp_list<TArgs...>>);
    
    template <class T, class... Ts> 
    void Register(std::function<std::shared_ptr<T> (Ts...)> && pFactory)
    {
        return RegisterImpl<T>(
            std::forward<std::function<std::shared_ptr<T> (Ts...)>>(pFactory),
            register_traits<Ts...>{});
    }
    

    And to call it:

    Register(std::function{[] (std::shared_ptr<Bar> bar) {
        return std::make_shared<Foo>(bar);
    }});
    

    Try it on godbolt.org.

    If boost::mp11 is not an option, here's how you can implement your own partition template metafunction:

    template <class...>
    struct list {};
    
    namespace detail {
    template <class L, template <class...> class P, class T, class F, class = void>
    struct partition;
    
    template <class Next, class... Ls,
              template <class...> class P, class T, class... Fs>
    struct partition<list<Next, Ls...>, P, T, list<Fs...>,
                     std::enable_if_t<!P<Next>::value>> :
           partition<list<Ls...>, P, T, list<Fs..., Next>> {};
    
    template <class Next, class... Ls,
              template <class...> class P, class... Ts, class F>
    struct partition<list<Next, Ls...>, P, list<Ts...>, F,
                     std::enable_if_t<P<Next>::value>> :
           partition<list<Ls...>, P, list<Ts..., Next>, F> {};
    
    template <template <class...> class P, class T, class F>
    struct partition<list<>, P, T, F> { using type = list<T, F>; };
    } // namespace detail
    
    template <class L, template <class...> class P>
    using partition = typename detail::partition<L, P, list<>, list<>>::type;
    
    template <class... Ts>
    using register_traits = partition<list<Ts...>, is_shared_ptr>;
    
    template <class T, class F, class... TDependencies, class... TArgs>
    void RegisterImpl(F && pFactory,
                      list<list<std::shared_ptr<TDependencies>...>, list<TArgs...>>);
    

    Try it on godbolt.org.

    The rest of the code will remain the same.