#ifndef KGR_KANGARU_INCLUDE_KANGARU_CONTAINER_HPP #define KGR_KANGARU_INCLUDE_KANGARU_CONTAINER_HPP #include "detail/traits.hpp" #include "detail/service_traits.hpp" #include "detail/utils.hpp" #include "detail/container_service.hpp" #include "detail/invoke.hpp" #include "detail/single.hpp" #include "detail/injected.hpp" #include "detail/error.hpp" #include "predicate.hpp" #include #include #include #include #include #include namespace kgr { struct Container final { private: template using enable_if = detail::enable_if_t; template using disable_if = detail::enable_if_t; template using instance_ptr = std::unique_ptr; using instance_cont = std::vector>; using service_cont = std::unordered_map; template using contained_service_t = typename std::conditional< detail::is_single::value, instance_ptr>, detail::Injected>::type; template static void deleter(void* i) { delete static_cast(i); } template static instance_ptr> make_instance_ptr(Args&&... args) { return instance_ptr>{ new detail::SingleInjected{std::forward(args)...}, &Container::deleter> }; } template static instance_ptr> make_override_ptr(Args&&... args) { return instance_ptr>{ new detail::ServiceOverride{std::forward(args)...}, &Container::deleter> }; } public: explicit Container() = default; Container(const Container &) = delete; Container& operator=(const Container &) = delete; Container(Container&&) = default; Container& operator=(Container&&) = default; ~Container() = default; /* * This function construct a service definition with the provided arguments. * It also saves it in the container. * It returns void. * This function only works with single services. * This version of the function will call functions in autocall. */ template> = 0, enable_if> = 0, enable_if> = 0> void instance(Args&&... args) { autocall(save_instance(make_contained_service(std::forward(args)...)).get()); } /* * This function construct a service definition with the provided arguments. * It also saves it in the container. * It returns void. * This function only works with single services. * This version of the function will not call functions in autocall. */ template> = 0, enable_if> = 0, enable_if> = 0> void instance(detail::no_autocall_t, Args&&... args) { save_instance(make_contained_service(std::forward(args)...)); } /* * This function returns the service given by serivce definition T. * T must be a valid service and must be constructible with arguments passed as parameters * In case of a non-single service, it takes additional arguments to be sent to the T::construct function. * T must not be a polymorphic type. */ template> = 0> ServiceType service(Args&&... args) { return definition(std::forward(args)...).forward(); } /* * The following two overloads are called in a case where the service is invalid, * or is called when provided arguments don't match the constructor. * In GCC, a diagnostic is provided. */ template>> = 0> detail::Sink service(detail::ServiceError = {}) = delete; template detail::Sink service(detail::ServiceError...>, Args&&...) = delete; /* * This function returns the result of the callable object of type U. * Args are additional arguments to be sent to the function after services arguments. * This function will deduce arguments from the function signature. */ template class Map = AdlMap, typename U, typename... Args, enable_if, Args...>> = 0> detail::invoke_function_result_t, Args...> invoke(U&& function, Args&&... args) { return invoke_helper( detail::tuple_seq_minus, Args...>, sizeof...(Args)>{}, std::forward(function), std::forward(args)... ); } /* * This function returns the result of the callable object of type U. * This version of the function is called when `function` is not invokable to provide diagnostic. */ template class Map> detail::Sink invoke(detail::NotInvokableError = {}, ...) = delete; /* * This function returns the result of the callable object of type U. * It will call the function with the sevices listed in the `Services` parameter pack. * It will call it in a equivalent expression of `std::declval()(std::declval>()..., std::declval()...)` */ template>..., detail::enable_if_t<(sizeof...(Services) > 0)>> = 0> auto invoke(U&& function, Args&&... args) -> decltype(std::declval()(std::declval>()..., std::declval()...)) { return std::forward(function)(service()..., std::forward(args)...); } /* * This oveload is called when one of the services to be injected is invalid. * It will provide diagnostic on GCC. */ template detail::Sink invoke(detail::NotInvokableError = {}, ...) = delete; /* * This function clears this container. * Every single services are invalidated after calling this function. */ inline void clear() { _instances.clear(); _services.clear(); } /* * This function fork the container into a new container. * The new container will have the copied state of the first container. * Construction of new services within the new container will not affect the original one. * The new container must exist within the lifetime of the original container. * * It takes a predicate type as template argument. * The default predicate is kgr::All. * * This version of the function takes a predicate that is default constructible. * It will call fork() with a predicate as parameter. */ template> = 0> Container fork() const { return fork(Predicate{}); } /* * This function fork the container into a new container. * The new container will have the copied state of the first container. * Construction of new services within the new container will not affect the original one. * The new container must exist within the lifetime of the original container. * * It takes a predicate as argument. */ template Container fork(Predicate&& predicate) const { Container c; c._services.reserve(_services.size()); std::copy_if( _services.begin(), _services.end(), std::inserter(c._services, c._services.begin()), [&predicate](service_cont::const_reference i){ // We don't forward the predicate here, we use it many times. return predicate(i.first); } ); return c; } /* * This function merges a container with another. * The receiving container will prefer it's own instances in a case of conflicts. */ inline void merge(Container&& other) { _instances.insert(_instances.end(), std::make_move_iterator(other._instances.begin()), std::make_move_iterator(other._instances.end())); _services.insert(other._services.begin(), other._services.end()); } /* * This function return true if the container contains the service T. * T nust be a single service. */ template> = 0, enable_if> = 0> bool contains() const { return _services.find(type_id) != _services.end(); } private: /////////////////////// // save instance // /////////////////////// /* * This function will create a new instance and save it. * It also returns a reference to the constructed service. */ template> = 0, disable_if> = 0> detail::BaseInjected& save_new_instance(Args&&... args) { auto& service = save_instance(make_service_instance(std::forward(args)...)); autocall(service.get()); return service; } /* * This function is a specialization of save_new_instance for abstract classes. * Since you cannot construct an abstract class, this function always throw. */ template> = 0, enable_if> = 0, disable_if> = 0> detail::BaseInjected& save_new_instance(Args&&...) { throw std::out_of_range{"No instance found for the requested abstract service"}; } /* * This function is a specialization of save_new_instance for abstract classes. * Since that abstract service has a default service specified, we can contruct that one. */ template> = 0, enable_if> = 0, enable_if> = 0> detail::BaseInjected& save_new_instance(Args&&...) { save_new_instance>(); // The static assert is still required here, if other checks fails and allow // a call to this function where the default service don't overrides T, it would be UB. static_assert( detail::is_overriden_by>::value, "The default service type of an abstract service must override that abstract serivce." ); // This could be faster if we had access to instance of override services. return *static_cast*>(_services[type_id]); } /* * This function will save the instance sent as arguments. * It receive a service that overrides. * It will save the instance and will save overrides if any. */ template detail::SingleInjected& save_instance(contained_service_t service) { return save_instance(detail::tuple_seq>{}, std::move(service)); } /* * This function saves the instance and it's overrides if any. * It starts the iteration that save each overrides and the service itself. */ template detail::SingleInjected& save_instance(detail::seq, contained_service_t service) { return save_instance_helper>...>(std::move(service)); } /* * This function save the instance into the container. * It also returns a reference to the instance inside the container. */ template detail::SingleInjected& save_instance_helper(contained_service_t service) { auto& serviceRef = *service; instance_ptr> injectedTypeService = std::move(service); _services[type_id] = injectedTypeService.get(); _instances.emplace_back(std::move(injectedTypeService)); return serviceRef; } /* * This function is the iteration that saves overrides to the container. * It will call itself until there is no override left. * It will class save_instance_helper with one template argument as it's last iteration. */ template detail::SingleInjected& save_instance_helper(contained_service_t service) { auto overrideService = make_override_ptr(service->get()); static_assert( std::is_same>, decltype(overrideService)>::value, "the override service must be the type instance_ptr>" ); _services[type_id] = overrideService.get(); _instances.emplace_back(std::move(overrideService)); return save_instance_helper(std::move(service)); } /////////////////////// // service // /////////////////////// /* * This function call service using the service map. * This function is called when the service map `Map` is valid for a given `T` */ template class Map, typename T, enable_if> = 0> auto mapped_service() -> decltype(service>()) { return service>(); } /////////////////////// // definition // /////////////////////// /* * This function returns a service definition. * This version of this function create the service each time it is called. */ template> = 0, disable_if> = 0> detail::Injected definition(Args&&... args) { auto service = make_service_instance(std::forward(args)...); autocall(service.get()); return service; } /* * This function returns a service definition. * This version of this function is specific to a container service. */ template> = 0> detail::Injected definition() { return detail::Injected{T{*this}}; } /* * This function returns a service definition. * This version of this function create the service if it was not created before. */ template> = 0, disable_if> = 0> detail::BaseInjected& definition() { if (auto service = _services[type_id]) { return *static_cast*>(service); } else { return save_new_instance(); } } /////////////////////// // make instance // /////////////////////// /* * This function creates an instance of a service. * It forward the work to make_service_instance_helper with an integer sequence. */ template contained_service_t make_service_instance(Args&&... args) { return make_service_instance_helper(detail::construct_result_seq{}, std::forward(args)...); } /* * This function is the helper for make_service_instance. * It construct the service using the values returned by construct. * It forward it's work to make_contained_service. */ template contained_service_t make_service_instance_helper(detail::seq, Args&&... args) { auto constructArgs = invoke_raw(detail::construct_function::value, std::forward(args)...); // This line is used to shut unused-variable warning, since S can be empty. static_cast(constructArgs); return make_contained_service(std::forward>(std::get(constructArgs))...); } /* * This function create a service with the received arguments. * It creating it in the right type for the container to contain it in it's container. */ template> = 0, enable_if> = 0> contained_service_t make_contained_service(Args&&... args) { return make_instance_ptr(in_place, std::forward(args)...); } /* * This function create a service with the received arguments. * It creating it in the right type for the container return it and inject it without overhead. */ template> = 0, enable_if> = 0> contained_service_t make_contained_service(Args&&... args) { return detail::Injected{in_place, std::forward(args)...}; } /* * This function create a service with the received arguments. * It creating it in the right type for the container to contain it in it's container. * This version of the function is called when the service definition has no valid constructor. * It will try to call an emplace function that construct the service in a lazy way. */ template> = 0, disable_if> = 0, enable_if> = 0> contained_service_t make_contained_service(Args&&... args) { auto service = make_instance_ptr(); service->get().emplace(std::forward(args)...); return service; } /* * This function create a service with the received arguments. * It creating it in the right type for the container return it and inject it without overhead. * This version of the function is called when the service definition has no valid constructor. * It will try to call an emplace function that construct the service in a lazy way. */ template> = 0, disable_if> = 0, enable_if> = 0> contained_service_t make_contained_service(Args&&... args) { detail::Injected service; service.get().emplace(std::forward(args)...); return service; } /////////////////////// // invoke // /////////////////////// /* * This function is an helper for the public invoke function. * It unpacks arguments of the function with an integer sequence. */ template class Map, typename U, typename... Args, std::size_t... S> detail::invoke_function_result_t, Args...> invoke_helper(detail::seq, U&& function, Args&&... args) { return std::forward(function)( mapped_service, Args...>>()..., std::forward(args)... ); } /* * This function is the same as invoke but it sends service definitions instead of the service itself. * It is called with some autocall function and the make_service_instance function. */ template detail::function_result_t> invoke_raw(U&& function, Args&&... args) { return invoke_raw_helper( detail::tuple_seq_minus>, sizeof...(Args)>{}, std::forward(function), std::forward(args)... ); } /* * This function is an helper of the invoke_raw function. * It unpacks arguments of the function U with an integer sequence. */ template detail::function_result_t> invoke_raw_helper(detail::seq, U&& function, Args&&... args) { return std::forward(function)(definition>>()..., std::forward(args)...); } /////////////////////// // autocall // /////////////////////// /* * This function starts the iteration (autocall_helper). */ template>> = 0> void autocall(T&& service) { autocall(detail::tuple_seq::Autocall>{}, std::forward(service)); } /* * This function is the iteration for autocall. */ template>> = 0> void autocall(detail::seq, T&& service) { using U = detail::decay_t; using unpack = int[]; (void)unpack{(invoke_autocall( detail::function_seq>{}, std::forward(service), detail::autocall_nth_function::value ), 0)..., 0}; } /* * This function is the invoke_autocall that take the method to invoke as parameter. * It invokes the function sent as parameter. */ template void invoke_autocall(detail::seq, T&& service, F&& function) { invoke_raw([&service, &function](detail::function_argument_t>... args) { (std::forward(service).*std::forward(function))(std::forward(args)...); }); } /* * This function is called when there is no autocall to do. */ template>> = 0> void autocall(T&&) {} /////////////////////// // instances // /////////////////////// instance_cont _instances; service_cont _services; }; } // namespace kgr #endif // KGR_KANGARU_INCLUDE_KANGARU_CONTAINER_HPP