// [Overview](#Overview) / [Examples](#Examples) / [API](#API) / [FAQ](#FAQ) / [Resources](#Resources) ## DI: Dependency Injection library [![MIT Licence](http://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/license/mit) [![Version](https://img.shields.io/github/v/release/qlibs/di)](https://github.com/qlibs/di/releases) [![Build](https://img.shields.io/badge/build-green.svg)](https://godbolt.org/z/fKEcojqze) [![Try it online](https://img.shields.io/badge/try%20it-online-blue.svg)](https://godbolt.org/z/xrzsYG1bj) > https://en.wikipedia.org/wiki/Dependency_injection (for additional info see [FAQ](#faq)) ### Features - Single header (https://raw.githubusercontent.com/qlibs/di/main/di) - Verifies itself upon include (can be disabled with `-DNTEST` - see [FAQ](#faq)) - Minimal [API](#api) - Unified way for different polymorphism styles (`inheritance, type erasure, variant, ...`) - [Generic factories](https://en.wikipedia.org/wiki/Factory_method_pattern) - Constructor deduction for classes and aggregates - Constructor order and types changes agnostic (simplifies integration with `third party` libraries) - Testing (different bindigns for `production` and `testing`, `faking` some parameters with `assisted` injection) - Policies (APIs with `checked` requirements) - Logging/Profiling/Serialization/... (via iteration over all `created` objects) ### Requirements - C++20 ([clang++13+, g++11+](https://en.cppreference.com/w/cpp/compiler_support)) --- ### Overview > API (https://godbolt.org/z/xrzsYG1bj) ```cpp struct aggregate1 { int i1{}; int i2{}; }; struct aggregate2 { int i2{}; int i1{}; }; struct aggregate { aggregate1 a1{}; aggregate2 a2{}; }; // di::make (basic) { static_assert(42 == di::make(42)); static_assert(aggregate1{1, 2} == di::make(1, 2)); } // di::make (generic) { auto a = di::make(di::overload{ [](di::trait auto) { return 42; } }); assert(a.i1 == 42); assert(a.i2 == 42); } // di::make (assisted) { struct assisted { constexpr assisted(int i, aggregate a, float f) : i{i}, a{a}, f{f} { } int i{}; aggregate a{}; float f{}; }; auto fakeit = [](auto t) { return decltype(t.type()){}; }; auto a = di::make(999, di::make(fakeit), 4.2f); assert(a.i == 999); assert(a.a.a1.i1 == 0); assert(a.a.a1.i2 == 0); assert(a.a.a2.i1 == 0); assert(a.a.a2.i2 == 0); assert(a.f == 4.2f); } // di::make (with names) { auto a = di::make(di::overload{ [](di::is auto t) requires (t.name() == "i1") { return 4; }, [](di::is auto t) requires (t.name() == "i2") { return 2; }, }); assert(a.i1 == 4); assert(a.i2 == 2); } // di::make (with names) - reverse order { auto a = di::make(di::overload{ [](di::is auto t) requires (t.name() == "i1") { return 4; }, [](di::is auto t) requires (t.name() == "i2") { return 2; }, }); assert(a.i1 == 4); assert(a.i2 == 2); } // di::make (with names, context and compound types) { auto a = di::make(di::overload{ // custom bindigs [](di::trait auto t) requires (t.name() == "i1" and &typeid(t.parent().type()) == &typeid(aggregate1)) { return 99; }, [](di::trait auto) { return 42; }, // generic bindings [](auto t) -> decltype(auto) { return di::make(t); }, // compund types }); assert(a.a1.i1 == 99); assert(a.a1.i2 == 42); assert(a.a2.i1 == 42); assert(a.a2.i2 == 42); } constexpr auto generic = di::overload{ [](auto t) -> decltype(auto) { return di::make(t); }, // compund types }; // di::make (seperate overloads) { constexpr auto custom = di::overload { [](di::trait auto t) requires (t.name() == "i1" and &typeid(t.parent().type()) == &typeid(aggregate1)) { return 99; }, [](di::trait auto t) { return decltype(t.type()){}; }, }; auto a = di::make(di::overload{custom, generic}); assert(a.a1.i1 == 99); assert(a.a1.i2 == 0); assert(a.a2.i1 == 0); assert(a.a2.i2 == 0); } // di::make (polymorphism, scopes) { struct interface { constexpr virtual ~interface() noexcept = default; constexpr virtual auto fn() const -> int = 0; }; struct implementation : interface { constexpr implementation(int i) : i{i} { } constexpr auto fn() const -> int override final { return i; } int i{}; }; struct example { example( aggregate& a, const std::shared_ptr& sp ) : a{a}, sp{sp} { } aggregate a{}; std::shared_ptr sp{}; }; auto i = 123; auto bindings = di::overload{ generic, [](di::is auto t) { return di::make(t); }, [&](di::is auto) -> decltype(auto) { return i; }, // instance // scopes [](di::trait auto t) -> decltype(auto) { using type = decltype(t.type()); static auto singleton{di::make>(t)}; return (singleton); }, }; auto e = di::make(bindings); assert(123 == e.sp->fn()); assert(123 == e.a.a1.i1); assert(123 == e.a.a1.i2); assert(123 == e.a.a2.i1); assert(123 == e.a.a2.i2); // testing (override bindings) { auto testing = di::overload{ [](di::trait auto) { return 1000; }, // priority [bindings](auto t) -> decltype(auto) { return bindings(t); }, // otherwise }; auto e = di::make(testing); assert(1000 == e.sp->fn()); assert(1000 == e.a.a1.i1); assert(1000 == e.a.a1.i2); assert(1000 == e.a.a2.i1); assert(1000 == e.a.a2.i2); } // logging { constexpr auto logger = [root = false]( di::provider&& t) mutable -> decltype(auto) { if constexpr (constexpr auto is_root = di::provider::size() == 1u; is_root) { if (not std::exchange(root, true)) { std::clog << reflect::type_name() << '\n'; } } for (auto i = 0u; i < di::provider::size(); ++i) { std::clog << ' '; } if constexpr (di::is_smart_ptr>) { std::clog << reflect::type_name() << '<' << reflect::type_name< typename std::remove_cvref_t::element_type>() << '>'; } else { std::clog << reflect::type_name(); } if constexpr (not di::is_smart_ptr> and requires { std::clog << std::declval(); }) { std::clog << ':' << t(t); } std::clog << '\n'; return t(t); }; (void)di::make(di::overload{logger, bindings}); // example // aggregate // aggregate1 // int:123 // int:123 // aggregate2 // int:123 // int:123 // shared_ptr -> implmentation // int:123 } } // policies { struct policy { constexpr policy(int*) { } }; [[maybe_unused]] auto p = di::make(di::overload{ []([[maybe_unused]] di::trait auto t) { static_assert(not sizeof(t), "raw pointers are not allowed!"); }, [](auto t) -> decltype(auto) { return di::make(t); }, // compund types }); // error } // errors { (void)di::make(di::overload{ // [](di::is auto) { return 42; }, // missing binding [](auto t) { return di::make(t); }, }); // di::error } // and more (see API)... ``` --- ### Examples > DIY - Dependency Injection Yourself (https://godbolt.org/z/acE3rYar5) ```cpp namespace di { inline constexpr auto injector = [](auto&&... ts) { return di::overload{ std::forward(ts)..., [](di::trait auto t) -> decltype(auto) { using type = decltype(t.type()); static auto singleton{di::make>(t)}; return (singleton); }, [](auto t) { return di::make(t); }, }; }; template inline constexpr auto bind = [] { if constexpr (std::is_void_v) { return [](T&& to) { return [&](di::is auto) -> decltype(auto) { return std::forward(to); }; }; } else { return [](di::is auto t) { return di::make(t); }; } }(); } // namespace di ``` ```cpp int main() { auto injector = di::injector( di::bind, di::bind(42) ); auto e = di::make(injector); assert(42 == e.sp->fn()); assert(42 == e.a.a1.i1); assert(42 == e.a.a1.i2); assert(42 == e.a.a2.i1); assert(42 == e.a.a2.i2); } ``` > Standard Template Library (https://godbolt.org/z/jjbnffKne) ```cpp struct STL { STL(std::vector vector, std::shared_ptr shared_ptr, std::unique_ptr unique_ptr, std::array array, std::string string) : vector(vector) , shared_ptr(shared_ptr) , unique_ptr(std::move(unique_ptr)) , array(array) , string(string) { } std::vector vector; std::shared_ptr shared_ptr; std::unique_ptr unique_ptr; std::array array; std::string string; }; int main() { auto stl = di::make( di::overload{ [](di::is> auto) { return std::vector{1, 2, 3}; }, [](di::is> auto) { return std::make_shared(1); }, [](di::is> auto) { return std::make_unique(2); }, [](di::is> auto) { return std::array{3}; }, [](di::is auto) { return std::string{"di"}; }, [](auto t) { return di::make(t); }, } ); assert(3u == stl.vector.size()); assert(1 == stl.vector[0]); assert(2 == stl.vector[1]); assert(3 == stl.vector[2]); assert(1 == *static_cast(stl.shared_ptr.get())); assert(2 == *stl.unique_ptr); assert(3 == stl.array[0]); assert("di" == stl.string); } ``` > `is_structural` - https://eel.is/c++draft/temp.param#def:type,structural (https://godbolt.org/z/1Mrxfbaqb) ```cpp template; if constexpr (requires { type{}; }) { return type{}; } else { return di::make(t); } } > concept is_structural = requires { [](cfg)>{}(); }; static_assert(is_structural); static_assert(not is_structural>); struct s { s() = delete; }; static_assert(not is_structural); struct y { int i; }; static_assert(is_structural); struct n { private: int i; }; static_assert(not is_structural); struct c1 { constexpr c1(int) {} }; static_assert(is_structural); struct c2 { constexpr c2(int, double) {} }; static_assert(is_structural); struct c3 { constexpr c3(std::optional) {} }; static_assert(not is_structural); struct c4 { constexpr c4(auto...) {} }; static_assert(is_structural); struct c5 { private: constexpr c5(auto...) {} }; static_assert(not is_structural); ``` ---- ### API ```cpp namespace di::inline v1_0_5 { /** * @code * struct c1 { c1(int) { } }; * static_assert(std::is_same_v, di::ctor_traits::type>); * #endcode */ template struct ctor_traits { template struct type_list{}; using type = type_list; [[nodiscard]] constexpr auto operator()(auto&&...) const -> T; }; /** * static_assert(di::invocable); * static_assert(di::invocable); */ template concept invocable; /** * @code * static_assert(not di::is); * static_assert(di::is); * @endcode */ template concept is; /** * @code * static_assert(not di::is_a); * static_assert(di::is_a, std::shared_ptr>); */ template class R> concept is_a; /** * @code * static_assert(not di::is_smart_ptr); * static_assert(di::is_smart_ptr>); */ template concept is_smart_ptr; /** * @code * static_assert(not di::trait); * static_assert(di::trait); */ template class Trait> concept trait; /** * @code * static_assert(42 == di::overload{ * [](int i) { return i; }, * [](auto a) { return a; } * }(42)); * @endcode */ template struct overload; /** * Injection context */ template struct provider { using value_type = T; using parent_type = TParent; static constexpr auto index() -> std::size_t; // index of parent constructor static constexpr auto parent() -> parent_type; // callee provider static constexpr auto type() -> value_type; // underlying type static constexpr auto size() -> std::size_t; // size of parents #if defined(REFLECT) static constexpr auto name() -> std::string_view; // member name #endif }; /** * @code * static_assert(42 == di::make(42)); * static_assert(42 == di::make( * di::overload{ * [](di::is auto) { return 42; } * } * )); * @endcode */ template [[nodiscard]] constexpr auto make(auto&&...); } // namespace di ``` --- ### FAQ > - Dependency Injection? > > Dependency Injection (DI) - https://en.wikipedia.org/wiki/Dependency_injection - it's a technique focusing on producing loosely coupled code. > > ```cpp > struct no_di { > constexpr no_di() { } // No DI > > private: > int data = 42; // coupled > }; > > struct di { > constexpr di(int data) : data{data} { } // DI > > private: > int data{}; > }; > ``` > > - In a very simplistic view, DI is about passing objects/types/etc via constructors and/or other forms of parameter propagating techniques instead of coupling values/types directly (`Hollywood Principle - Don't call us we'll call you`). > - The main goal of DI is the flexibility of changing what's being injected. It's important though, what and how is being injected as that influences how good (`ETC - Easy To Change`) the design will be - more about it here - https://www.youtube.com/watch?v=yVogS4NbL6U. > > - Manual vs Automatic Dependency Injection? > > Depedency Injection doesnt imply using a library. > Automatic DI requires a library and makes more sense for larger projects as it helps limitting the wiring mess and the maintenance burden assosiated with it. > > ```cpp > struct coffee_maker { > coffee_maker(); // No DI > > private: > basic_heater heater{}; // coupled > basic_pump pump{}; // coupled > }; > > struct coffee_maker_v1 { > coffee_maker(iheater&, ipump& pump); // DI > > private: > iheater& heater; // not coupled > ipump& pump; // not coupled > }; > > struct coffee_maker_v2 { > coffee_maker(std::shared_ptr, std::unique_ptr); // DI > > private: > std::shared_ptr pump; // not coupled > std::unique_ptr heater; // not coupled > }; > > int main() { > // Manual Dependency Injection > { > basic_heater heater{}; > basic_pump pump{}; > coffe_maker_v1 cm{heater, pump}; > } > { > auto pump = std::make_shared(); > auto heater = std::make_unique(); > coffe_maker_v2 cm{pump, std::move(heater)}; // different wiring > } > > // Automatic Dependency Injection > auto wiring = di::overload{ > [](di::is auto) { return make(); }, > [](di::is auto) { return make(); }, > }; > { > auto cm = di::make(wiring); > } > { > auto cm = di::make(wiring); // same wiring > } > } > ``` > > The main goal of automatic is to **avoid design compromises** in order to reduce the boilerplate code/minimize maintance burden/simplify testing. > > - How does it work? > > `DI` works by deducing constructor parameters and calling appropriate overload to handle them by leavaring concepts - https://eel.is/c++draft/temp.constr.order#def:constraint,subsumption. > The following represents the most important parts of the library design. > > ```cpp > template > concept copy_or_move = std::is_same_v>; > > template struct any { > template requires (not copy_or_move) > operator T() noexcept(noexcept(bind, T>{})); > template requires (not copy_or_move) > operator T&() const noexcept(noexcept(bind, T&>{})); > template requires (not copy_or_move) > operator const T&() const noexcept(noexcept(bind, const T&>{})); > template requires (not copy_or_move) > operator T&&() const noexcept(noexcept(bind, T&&>{})); > }; > ``` > > ```cpp > template constexpr auto ctor_traits() { > return [](std::index_sequence) { > if constexpr (requires { T{any{}...}; }) { > return type_list{}))...>{}; > } else if constexpr (sizeof...(Ns)) { > return ctor_traits(); > } else { > return type_list{}; > } > }(std::make_index_sequence{}); > } > ``` > > ```cpp > template struct overload : Ts... { using Ts::operator()...; }; > template overload(Ts...) -> overload; > ``` > > ```cpp > template auto error(auto&&...) -> T; > template constexpr auto make(invocable auto&& t) { > return [&] class TList, class... Ts>(TList) { > if constexpr (requires { T{t(provider(t)...); }; }) { > return T{t(provider(t)...}; > } else { > return error(t); > } > }(ctor_traits()); > }; > ``` > > - How to disable running tests at compile-time? > > When `-DNTEST` is defined static_asserts tests wont be executed upon include. > Note: Use with caution as disabling tests means that there are no gurantees upon include that given compiler/env combination works as expected. > > - Similar projects? > [boost-ext.di](https://github.com/boost-ext/di), [google.fruit](https://github.com/google/fruit), [kangaru](https://github.com/gracicot/kangaru), [wallaroo](https://wallaroolib.sourceforge.net), [hypodermic](https://github.com/ybainier/Hypodermic), [dingo](https://github.com/romanpauk/dingo) ### Resources > - ["Dependency Injection - a 25-dollar term for a 5-cent concept"](https://www.youtube.com/watch?v=yVogS4NbL6U) (video) > - ["Law of Demeter: A Practical Guide to Loose Coupling"](https://www.youtube.com/watch?v=QZkVpZlbM4U) (video) > - ["Clean Code: A Handbook of Agile Software Craftsmanship"](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) (book) > - ["The Pragmatic Programmer"](https://www.amazon.com/Pragmatic-Programmer-journey-mastery-Anniversary/dp/0135957052/ref=pd_sbs_d_sccl_3_1/140-7224166-5387863?pd_rd_w=muV95&content-id=amzn1.sym.156274ff-6322-443d-8bbf-ab3ed87e382f&pf_rd_p=156274ff-6322-443d-8bbf-ab3ed87e382f&pf_rd_r=ECRZDQ02XA134FQJJQDE&pd_rd_wg=ELPtc&pd_rd_r=49d9e9b3-a8b1-4532-b3b3-16711496a3d3&pd_rd_i=0135957052&psc=1) (book) > - ["Design Patterns"](https://www.amazon.com/Design-Patterns-Object-Oriented-Addison-Wesley-Professional-ebook/dp/B000SEIBB8) (book) > - ["Test Driven Development: By Example"](https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530) (book) ### License > - [MIT](LICENSE)