# librtdi **Repository Path**: paul3rd/librtdi ## Basic Information - **Project Name**: librtdi - **Description**: librtdi -- C++20 Runtime Dependency Injection Framework - **Primary Language**: C++ - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-02-25 - **Last Updated**: 2026-03-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # librtdi -- C++20 Runtime Dependency Injection Framework `librtdi` is a C++20 DI/IoC framework inspired by .NET `Microsoft.Extensions.DependencyInjection` and Google `fruit`, featuring: - Runtime type erasure (`erased_ptr` + `type_index`) - Zero-macro dependency declaration (`deps<>`) - Lifetime management (`singleton` / `transient`) - Four-slot model (single-instance + collection x singleton + transient) - Multi-implementation, keyed registration, forward, decorator - `build()`-time validation (missing deps, cycles, lifetime violations) - Multiple and virtual inheritance support ## Quick Start ### Minimal Example ```cpp #include #include struct IGreeter { virtual ~IGreeter() = default; virtual void greet() const = 0; }; struct ConsoleGreeter : IGreeter { void greet() const override { std::cout << "Hello, DI!\n"; } }; int main() { using namespace librtdi; registry reg; reg.add_singleton(); auto r = reg.build(); r->get().greet(); // returns IGreeter& } ``` ### Registration with Dependencies (`deps<>`) ```cpp struct ILogger { virtual ~ILogger() = default; virtual void log(std::string_view msg) const = 0; }; struct ConsoleLogger : ILogger { void log(std::string_view msg) const override { std::cout << "[LOG] " << msg << "\n"; } }; struct IService { virtual ~IService() = default; virtual void run() = 0; }; // Dependencies are injected via constructor reference struct MyService : IService { ILogger& logger_; explicit MyService(ILogger& logger) : logger_(logger) {} void run() override { logger_.log("running"); } }; int main() { using namespace librtdi; registry reg; reg.add_singleton(); reg.add_singleton(deps); // ^^^^^^^^^^^ // deps -> injects ILogger& (singleton reference) auto r = reg.build(); r->get().run(); } ``` ## Core Concepts ### Lifetimes | Enum | Meaning | Resolution API | |------|---------|----------------| | `singleton` | Global unique, created eagerly during `build()` by default | `get()` returns `T&` | | `transient` | New instance on every resolve | `create()` returns `unique_ptr` | ### Four-Slot Model Each `(type, key)` pair can have up to 4 independent slots: | Slot | Registration Method | Injection Type | |------|---------------------|----------------| | Singleton single | `add_singleton()` | `T&` | | Transient single | `add_transient()` | `unique_ptr` | | Singleton collection | `add_collection(lifetime_kind::singleton)` | `vector` | | Transient collection | `add_collection(lifetime_kind::transient)` | `vector>` | ### Dependency Tags Each type parameter in `deps<>` determines the injection style: | Tag | Injection Type | Resolver Method | |-----|----------------|-----------------| | `T` / `singleton` | `T&` | `get()` | | `transient` | `unique_ptr` | `create()` | | `collection` | `vector` | `get_all()` | | `collection>` | `vector>` | `create_all()` | ## Registration API ### Single-Instance Registration ```cpp using namespace librtdi; registry reg; // Zero-dep singleton reg.add_singleton(); // Singleton with dependencies reg.add_singleton(deps>); // Zero-dep transient reg.add_transient(); // Transient with dependencies reg.add_transient(deps); ``` ### Collection Registration ```cpp // Multiple implementations registered to the same interface (freely appendable) reg.add_collection(lifetime_kind::singleton); reg.add_collection(lifetime_kind::singleton); // Consumer receives all implementations via collection reg.add_singleton(deps>); ``` ### Keyed Registration ```cpp reg.add_singleton("redis"); reg.add_singleton("memory"); auto r = reg.build({.validate_on_build = false}); auto& redis = r->get("redis"); auto& mem = r->get("memory"); ``` ### Forward Registration Expose one implementation through multiple interfaces; singletons share the same instance: ```cpp struct Impl : IA, IB { Impl() = default; }; reg.add_singleton(); reg.forward(); // IA singleton shares Impl's instance reg.forward(); // IB likewise auto r = reg.build(); auto& a = r->get(); // same underlying object as get() auto& b = r->get(); // same underlying object as get() ``` ### Decorator Transparently wrap registered implementations with additional logic: ```cpp struct LoggingFoo : IFoo { librtdi::decorated_ptr inner_; explicit LoggingFoo(librtdi::decorated_ptr inner) : inner_(std::move(inner)) {} void do_something() override { std::cout << "before\n"; inner_->do_something(); std::cout << "after\n"; } }; reg.add_singleton(); reg.decorate(); // Type-safe targeted decoration (compile-time check that TTarget derives from I) reg.decorate_target(); ``` Multiple decorators stack in registration order: first registered is innermost, last is outermost. ## Resolution API ```cpp auto r = reg.build(); // Singleton (reference, lifetime bound to resolver) auto& svc = r->get(); // IFoo&, throws not_found if unregistered auto* ptr = r->try_get(); // IFoo* or nullptr // Transient (ownership transferred to caller) auto obj = r->create(); // unique_ptr, throws not_found if unregistered auto opt = r->try_create(); // unique_ptr or empty // Collections auto all = r->get_all(); // vector (singleton collection) auto allT = r->create_all(); // vector> (transient collection) // Keyed variants auto& redis = r->get("redis"); auto conn = r->create("primary"); ``` ## Build-Time Validation `build()` automatically validates before creating the resolver: ```cpp auto r = reg.build(); // all validations enabled by default ``` Optional controls: ```cpp auto r = reg.build({ .validate_on_build = true, // master switch .validate_lifetimes = true, // check captive dependency .detect_cycles = true, // check circular dependencies .eager_singletons = true, // instantiate all singletons during build() }); ``` When `eager_singletons` is `true` (default), all singleton factories are invoked during `build()`, so factory exceptions surface immediately and first-request latency is eliminated. Set to `false` for lazy initialization. Validation order: missing dependencies, then lifetime compatibility, then cycle detection, then eager singleton instantiation. ## Inheritance Model librtdi supports all C++ inheritance forms: | Inheritance | Supported | Notes | |-------------|-----------|-------| | Single | Yes | Standard scenario | | Multiple | Yes | Pointer offset handled automatically via `make_erased_as()` | | Virtual | Yes | Upcast `static_cast` (derived to base) is legal for virtual inheritance | | Diamond | Yes | Used in combination with virtual inheritance | **Requirement**: When `TInterface != TImpl`, `TInterface` must have a virtual destructor (enforced by compile-time `static_assert`). ## Exception Hierarchy ``` std::runtime_error +-- di_error <-- base for all DI exceptions +-- not_found <-- no registration found +-- cyclic_dependency <-- circular dependency detected +-- lifetime_mismatch <-- lifetime violation (captive dependency) +-- duplicate_registration <-- duplicate single-instance slot registration +-- resolution_error <-- wraps factory exceptions ``` All exception messages include demangled type names and source location (pointing to the user's call site, not library internals). Key diagnostic features: - **`source_location` accuracy**: All public template methods capture `std::source_location::current()` at the user call site, ensuring exception locations are meaningful - **Registration location tracking**: `descriptor` stores where each component was registered; `resolution_error` and `not_found` messages include "(registered at file:line)" - **Consumer info in `not_found`**: Validation-phase `not_found` messages include the consumer type, impl type, lifetime, and registration location - **Impl info in `lifetime_mismatch`**: Messages optionally include the concrete implementation type name - **Non-standard exception pass-through**: Factory exceptions that don't derive from `std::exception` are not caught — they propagate to the caller as-is - **Contextual `static_assert`**: Each compile-time assertion names the specific API (e.g., `"add_singleton: I must have a virtual destructor when I != T"`) - **Slot hints in `not_found`**: When the type exists in a different slot (e.g., registered as transient but requested via `get()`), the message suggests the correct method - **Registration stacktrace** (Boost.Stacktrace): When enabled (default ON, `cmake -DLIBRTDI_ENABLE_STACKTRACE=OFF` to disable), every registration captures a full call stack. On build-time validation or resolution errors, call `e.full_diagnostic()` to get `what()` plus the complete registration-time stacktrace — invaluable for tracing back through layers of user code that led to a misconfiguration ## Thread Safety - **Registration phase**: `registry` assumes single-threaded use - **Resolution phase**: `resolver` is safe for concurrent multi-threaded use; singleton creation is protected by a `recursive_mutex` ensuring once-per-descriptor semantics ## Building librtdi builds as a **shared library** (`librtdi.so` / `librtdi.dylib` / `rtdi.dll`) by default. ```bash cmake -B build -G Ninja cmake --build build # Run tests ctest --test-dir build --output-on-failure # Package cmake --build build --target package ``` ### Cross-Platform Notes | Platform | Library Output | Notes | |----------|----------------|-------| | Linux | `librtdi.so.0.1.1` (SOVERSION symlinks) | `-fvisibility=hidden`; only `LIBRTDI_EXPORT` symbols exported | | macOS | `librtdi.0.1.1.dylib` | `MACOSX_RPATH ON`; `@loader_path` RPATH | | Windows | `rtdi.dll` + `rtdi.lib` (import lib) | `__declspec(dllexport/dllimport)` via `LIBRTDI_EXPORT` macro | To build and link as a **static library**, define `LIBRTDI_STATIC` before including any librtdi header, and change `SHARED` to `STATIC` in `src/CMakeLists.txt`. ### Downstream Integration After installation, integrate via standard CMake `find_package`: ```cmake find_package(librtdi CONFIG REQUIRED) target_link_libraries(my_app PRIVATE librtdi::librtdi) ``` ## Full Example See [examples/basic_usage.cpp](examples/basic_usage.cpp) for a complete demo of singleton, deps injection, collections, and decorator composition. ## Project Structure ```text librtdi/ ├── CMakeLists.txt ├── README.md ├── cmake/ │ ├── CompilerWarnings.cmake │ ├── Dependencies.cmake │ └── librtdiConfig.cmake.in ├── docs/ │ └── REQUIREMENTS.md ├── include/ │ ├── librtdi.hpp │ └── librtdi/ │ ├── export.hpp │ ├── fwd.hpp │ ├── lifetime.hpp │ ├── erased_ptr.hpp │ ├── decorated_ptr.hpp │ ├── descriptor.hpp │ ├── exceptions.hpp │ ├── registry.hpp │ ├── resolver.hpp │ └── type_traits.hpp ├── src/ │ ├── exceptions.cpp │ ├── registry.cpp │ ├── resolver.cpp │ └── validation.cpp ├── tests/ │ ├── test_auto_wiring.cpp │ ├── test_concurrency.cpp │ ├── test_decorator.cpp │ ├── test_diagnostics.cpp │ ├── test_eager.cpp │ ├── test_edge_cases.cpp │ ├── test_forward.cpp │ ├── test_inheritance.cpp │ ├── test_keyed.cpp │ ├── test_lifetime.cpp │ ├── test_multi_impl.cpp │ ├── test_registration.cpp │ ├── test_resolution.cpp │ └── test_validation.cpp └── examples/ └── basic_usage.cpp ```