lintel 0.2.0
Loading...
Searching...
No Matches
Container.h
Go to the documentation of this file.
1#ifndef LINTEL_CORE_DI_CONTAINER_H
2#define LINTEL_CORE_DI_CONTAINER_H
3
4#include <algorithm>
5#include <cstddef>
6#include <functional>
7#include <memory>
8#include <mutex>
9#include <stdexcept>
10#include <string>
11#include <type_traits>
12#include <typeindex>
13#include <unordered_map>
14#include <utility>
15#include <vector>
16
18
19namespace lintel {
20namespace di {
21
22 class Container;
23 class ContainerBuilder;
24
26 class ResolutionException : public std::runtime_error
27 {
28 public:
30 explicit ResolutionException(const std::string &message) : std::runtime_error(message) {}
31 };
32
33 namespace detail {
34
36 constexpr std::size_t kMaxConstructorArguments = 20;
37
39 template<typename T> struct IsSupportedArgument : std::false_type
40 {
41 };
42
43 template<typename T> struct IsSupportedArgument<std::shared_ptr<T>> : std::true_type
44 {
45 };
46
47 template<typename T> struct IsSupportedArgument<std::vector<std::shared_ptr<T>>> : std::true_type
48 {
49 };
50
52 template<typename TDependency> struct ArgumentResolver;
53
54 template<typename TDependency> struct ArgumentResolver<std::shared_ptr<TDependency>>
55 {
56 template<typename TContainer> static std::shared_ptr<TDependency> resolve(TContainer &container)
57 {
58 return container.template resolve<TDependency>();
59 }
60 };
61
62 template<typename TDependency> struct ArgumentResolver<std::vector<std::shared_ptr<TDependency>>>
63 {
64 template<typename TContainer> static std::vector<std::shared_ptr<TDependency>> resolve(TContainer &container)
65 {
66 return container.template resolveAll<TDependency>();
67 }
68 };
69
71 template<typename TOwner> struct InjectorArgument
72 {
74
75 template<typename T,
76 typename =
77 std::enable_if_t<IsSupportedArgument<std::decay_t<T>>::value && !std::is_convertible<TOwner, T>::value>>
78 operator T() const
79 {
81 }
82 };
83
85 template<typename T, std::size_t> using InjectorFor = InjectorArgument<T>;
86
87 template<typename T, std::size_t... Indices> constexpr bool isInjectableWith(std::index_sequence<Indices...>)
88 {
89 return std::is_constructible_v<T, InjectorFor<T, Indices>...>;
90 }
91
92 template<typename T, std::size_t N> constexpr bool isInjectableWith()
93 {
94 return isInjectableWith<T>(std::make_index_sequence<N>{});
95 }
96
97 template<typename T, std::size_t N> constexpr std::size_t constructorArity()
98 {
99 if constexpr (N == 0) {
100 if constexpr (std::is_constructible_v<T>) {
101 return 0;
102 } else {
103 return constructorArity<T, 1>();
104 }
105 } else if constexpr (N > kMaxConstructorArguments) {
106 return kMaxConstructorArguments + 1;
107 } else if constexpr (isInjectableWith<T, N>()) {
108 return N;
109 } else {
111 }
112 }
113
115 template<typename T> constexpr bool hasInjectableConstructor()
116 {
117 if constexpr (!std::is_class_v<T> || std::is_abstract_v<T>) {
118 return false;
119 } else {
121 }
122 }
123
124 template<typename T, std::size_t... Indices>
125 std::shared_ptr<T> createWithInjectors(Container &container, std::index_sequence<Indices...>)
126 {
127 return std::make_shared<T>(InjectorFor<T, Indices>{ container }...);
128 }
129
130 template<typename T> std::shared_ptr<T> createInstance(Container &container)
131 {
132 return createWithInjectors<T>(container, std::make_index_sequence<constructorArity<T, 0>()>{});
133 }
134
136 inline std::vector<const void *> &activeRegistrations()
137 {
138 thread_local std::vector<const void *> active;
139 return active;
140 }
141
144 {
145 explicit ActivationScope(const void *registration) : m_registration(registration)
146 {
147 auto &active = activeRegistrations();
148 if (std::find(active.begin(), active.end(), m_registration) != active.end()) {
149 throw ResolutionException("circular dependency detected while creating an instance");
150 }
151 active.push_back(m_registration);
152 }
156
157 private:
158 const void *m_registration;
159 };
160
161 }// namespace detail
162
165 {
167 std::vector<std::type_index> keys;
169 bool singleton = false;
171 std::function<std::shared_ptr<void>(Container &)> factory;
173 std::unordered_map<std::type_index, std::function<std::shared_ptr<void>(const std::shared_ptr<void> &)>> casts;
175 std::shared_ptr<void> instance;
177 std::mutex mutex;
178
180 std::shared_ptr<void> castTo(const std::type_index &key, const std::shared_ptr<void> &concrete) const
181 {
182 auto found = casts.find(key);
183 if (found == casts.end()) { return concrete; }
184 return found->second(concrete);
185 }
186 };
187
190 {
191 public:
192 Container() = default;
193 Container(const Container &) = delete;
194 Container &operator=(const Container &) = delete;
195
200 template<typename T> std::shared_ptr<T> resolve()
201 {
202 std::shared_ptr<Registration> registration;
203 {
204 std::lock_guard<std::mutex> lock(m_mutex);
205 auto found = m_registrations.find(std::type_index(typeid(T)));
206 if (found != m_registrations.end() && !found->second.empty()) { registration = found->second.back(); }
207 }
209 if (!registration) {
210 throw ResolutionException(std::string("no registration for ") + std::string(type_name<T>()));
211 }
212 return std::static_pointer_cast<T>(registration->castTo(std::type_index(typeid(T)), instantiate(registration)));
213 }
214
218 template<typename T> std::vector<std::shared_ptr<T>> resolveAll()
219 {
220 std::vector<std::shared_ptr<Registration>> registrations;
221 {
222 std::lock_guard<std::mutex> lock(m_mutex);
223 auto found = m_registrations.find(std::type_index(typeid(T)));
224 if (found != m_registrations.end()) { registrations = found->second; }
225 }
226 std::vector<std::shared_ptr<T>> instances;
227 instances.reserve(registrations.size());
228 for (const auto &registration : registrations) {
229 instances.push_back(
230 std::static_pointer_cast<T>(registration->castTo(std::type_index(typeid(T)), instantiate(registration))));
231 }
232 return instances;
233 }
234
235 private:
236 friend class ContainerBuilder;
237
238 void addRegistration(const std::shared_ptr<Registration> &registration);
239 std::shared_ptr<void> instantiate(const std::shared_ptr<Registration> &registration);
240 template<typename T> std::shared_ptr<Registration> tryAutoRegister();
241
242 std::unordered_map<std::type_index, std::vector<std::shared_ptr<Registration>>> m_registrations;
243 std::mutex m_mutex;
244 };
245
247 template<typename TInstance> class RegistrationBuilder
248 {
249 public:
251 explicit RegistrationBuilder(std::shared_ptr<Registration> registration) : m_registration(std::move(registration))
252 {}
253
255 template<typename TInterface> RegistrationBuilder &as()
256 {
257 m_registration->keys.push_back(std::type_index(typeid(TInterface)));
258 m_registration->casts[std::type_index(typeid(TInterface))] =
259 [](const std::shared_ptr<void> &instance) -> std::shared_ptr<void> {
260 return std::static_pointer_cast<void>(
261 std::static_pointer_cast<TInterface>(std::static_pointer_cast<TInstance>(instance)));
262 };
263 return *this;
264 }
265
267 RegistrationBuilder &asSelf() { return *this; }
268
271 {
272 m_registration->singleton = true;
273 return *this;
274 }
275
276 private:
277 std::shared_ptr<Registration> m_registration;
278 };
279
282 {
283 public:
286 template<typename TInstance> void registerInstance(std::shared_ptr<TInstance> instance)
287 {
288 std::shared_ptr<void> shared = std::move(instance);
289 auto registration = std::make_shared<Registration>();
290 registration->keys.push_back(std::type_index(typeid(TInstance)));
291 registration->casts[std::type_index(typeid(TInstance))] =
292 [](const std::shared_ptr<void> &instance) -> std::shared_ptr<void> { return instance; };
293 registration->singleton = true;
294 registration->instance = shared;
295 registration->factory = [shared](Container &) -> std::shared_ptr<void> { return shared; };
296 m_registrations.push_back(std::move(registration));
297 }
298
301 template<typename TInstance> RegistrationBuilder<TInstance> registerType()
302 {
303 static_assert(detail::hasInjectableConstructor<TInstance>(),
304 "registerType<T> requires T to be default-constructible or to have a constructor whose parameters are "
305 "std::shared_ptr<...> or std::vector<std::shared_ptr<...>>");
306 auto registration = std::make_shared<Registration>();
307 registration->keys.push_back(std::type_index(typeid(TInstance)));
308 registration->casts[std::type_index(typeid(TInstance))] =
309 [](const std::shared_ptr<void> &instance) -> std::shared_ptr<void> { return instance; };
310 registration->factory = [](Container &container) -> std::shared_ptr<void> {
311 return std::static_pointer_cast<void>(detail::createInstance<TInstance>(container));
312 };
313 m_registrations.push_back(registration);
315 }
316
319 std::shared_ptr<Container> build()
320 {
321 auto container = std::make_shared<Container>();
322 for (const auto &registration : m_registrations) { container->addRegistration(registration); }
323 // The container is resolvable as itself so injected services and bootstrap
324 // plugins receive the real container (e.g. VirtualGroupBootstrapPlugin
325 // resolves all registered GroupProviders). A weak_ptr keeps this from
326 // creating a container <-> registration reference cycle.
327 auto selfRegistration = std::make_shared<Registration>();
328 selfRegistration->keys.push_back(std::type_index(typeid(Container)));
329 selfRegistration->casts[std::type_index(typeid(Container))] =
330 [](const std::shared_ptr<void> &instance) -> std::shared_ptr<void> { return instance; };
331 std::weak_ptr<Container> weak = container;
332 selfRegistration->factory = [weak](Container &) -> std::shared_ptr<void> {
333 return std::static_pointer_cast<void>(weak.lock());
334 };
335 container->addRegistration(selfRegistration);
336 return container;
337 }
338
339 private:
340 std::vector<std::shared_ptr<Registration>> m_registrations;
341 };
342
343 inline void Container::addRegistration(const std::shared_ptr<Registration> &registration)
344 {
345 std::lock_guard<std::mutex> lock(m_mutex);
346 for (const auto &key : registration->keys) { m_registrations[key].push_back(registration); }
347 }
348
349 inline std::shared_ptr<void> Container::instantiate(const std::shared_ptr<Registration> &registration)
350 {
351 detail::ActivationScope scope(registration.get());
352 if (!registration->singleton) { return registration->factory(*this); }
353 std::lock_guard<std::mutex> lock(registration->mutex);
354 if (!registration->instance) { registration->instance = registration->factory(*this); }
355 return registration->instance;
356 }
357
358 template<typename T> std::shared_ptr<Registration> Container::tryAutoRegister()
359 {
360 if constexpr (!detail::hasInjectableConstructor<T>()) {
361 return nullptr;
362 } else {
363 std::lock_guard<std::mutex> lock(m_mutex);
364 auto &registrations = m_registrations[std::type_index(typeid(T))];
365 if (!registrations.empty()) { return registrations.back(); }
366 auto registration = std::make_shared<Registration>();
367 registration->keys.push_back(std::type_index(typeid(T)));
368 registration->casts[std::type_index(typeid(T))] =
369 [](const std::shared_ptr<void> &instance) -> std::shared_ptr<void> { return instance; };
370 registration->factory = [](Container &container) -> std::shared_ptr<void> {
371 return std::static_pointer_cast<void>(detail::createInstance<T>(container));
372 };
373 registrations.push_back(registration);
374 return registration;
375 }
376 }
377
378}// namespace di
379}// namespace lintel
380
381#endif// LINTEL_CORE_DI_CONTAINER_H
Definition Container.h:282
RegistrationBuilder< TInstance > registerType()
Definition Container.h:301
std::shared_ptr< Container > build()
Definition Container.h:319
void registerInstance(std::shared_ptr< TInstance > instance)
Definition Container.h:286
Definition Container.h:190
std::vector< std::shared_ptr< T > > resolveAll()
Definition Container.h:218
std::shared_ptr< T > resolve()
Definition Container.h:200
Container & operator=(const Container &)=delete
Container(const Container &)=delete
Definition Container.h:248
RegistrationBuilder & asSelf()
Definition Container.h:267
RegistrationBuilder & as()
Definition Container.h:255
RegistrationBuilder(std::shared_ptr< Registration > registration)
Definition Container.h:251
RegistrationBuilder & singleInstance()
Definition Container.h:270
Definition Container.h:27
ResolutionException(const std::string &message)
Definition Container.h:30
constexpr bool isInjectableWith()
Definition Container.h:92
constexpr bool hasInjectableConstructor()
Definition Container.h:115
constexpr std::size_t constructorArity()
Definition Container.h:97
std::shared_ptr< T > createInstance(Container &container)
Definition Container.h:130
std::shared_ptr< T > createWithInjectors(Container &container, std::index_sequence< Indices... >)
Definition Container.h:125
constexpr std::size_t kMaxConstructorArguments
Definition Container.h:36
std::vector< const void * > & activeRegistrations()
Definition Container.h:136
Definition BaseFeature.h:6
constexpr std::string_view type_name()
Definition TypeName.h:9
Definition Container.h:165
std::shared_ptr< void > instance
Definition Container.h:175
std::unordered_map< std::type_index, std::function< std::shared_ptr< void >(const std::shared_ptr< void > &)> > casts
Definition Container.h:173
std::function< std::shared_ptr< void >(Container &)> factory
Definition Container.h:171
bool singleton
Definition Container.h:169
std::shared_ptr< void > castTo(const std::type_index &key, const std::shared_ptr< void > &concrete) const
Definition Container.h:180
std::vector< std::type_index > keys
Definition Container.h:167
std::mutex mutex
Definition Container.h:177
Definition Container.h:144
ActivationScope(const ActivationScope &)=delete
ActivationScope(const void *registration)
Definition Container.h:145
~ActivationScope()
Definition Container.h:153
ActivationScope & operator=(const ActivationScope &)=delete
static std::shared_ptr< TDependency > resolve(TContainer &container)
Definition Container.h:56
static std::vector< std::shared_ptr< TDependency > > resolve(TContainer &container)
Definition Container.h:64
Definition Container.h:52
Definition Container.h:72
Container & container
Definition Container.h:73