Introduction
Capydi is a modern, header-only dependency injection framework designed for C++23 and beyond. It leverages compile-time concepts, template metaprogramming, and constexpr to provide type-safe, zero-overhead dependency resolution with an elegant API.
The framework emphasizes:
- Type Safety: Concepts enforce correct configuration and usage patterns at compile time
- Zero Overhead: All resolution is compile-time; no runtime polymorphism or reflection
- Elegance: Clean, intuitive API that reads naturally while maintaining power
- Flexibility: Support for both creational patterns and decorator/chainable transformations
Architecture
Core Concepts
DiConfig
A marker concept that all configurations must satisfy. Configurations are classified into two categories:
- CreationalConfig: Handles instantiation of objects (factories, constructors, etc.)
- ChainableConfig: Handles transformation/decoration of objects (logging, caching, etc.)
Reference
An abstraction for referring to objects that supports both runtime (RuntimeRef) and compile-time (ConstexprRef) scenarios. All transformations in the decorator chain work through the Reference concept.
Resolution
The result type of a dependency query: std::expected<Reference<T>, Error>. Enables elegant error handling without exceptions.
Basic Usage
Creating a Container
#include <capydi/configs/creational/Factory.hpp>
class Logger {
public:
void log(const std::string& msg) { std::cout << msg << '\n'; }
};
class MyService {
public:
void doWork() { std::cout << "Working...\n"; }
};
capy::di::Factory<Logger>{},
capy::di::Factory<MyService>{},
);
auto result = di.
resolve<LoggerKey>();
if (result.has_value()) {
auto& logger = result.value().get();
logger.log("Dependency resolved!");
}
Core dependency injection container for managing creational and chainable configurations.
Primary dependency injection container managing configurations and resolution.
Definition Container.hpp:87
constexpr Resolution< Type, Error > auto resolve(InputType &&input=std::tuple{}) const
Definition Container.hpp:134
Definition Decorator.hpp:30
Using Decorators
Decorators form a transformation pipeline:
class CachingDecorator {
public:
using RelatedEntity = MyService;
using RelatedKey = CacheKey;
static constexpr ConfigType CONFIG_TYPE = ConfigType::CHAINABLE;
capy::di::Reference<MyService> auto pipe(
capy::di::Reference<MyService> auto service
) const {
return capy::di::RuntimeRef(cached_instance);
}
};
Factory<MyService>{},
CachingDecorator{},
LoggingDecorator{}
);
Design Philosophy
Capydi is built on several core principles:
- Compile-Time First: Leverage C++20 concepts and templates to catch errors early
- Zero-Cost Abstraction: No runtime overhead for what happens at compile time
- Explicit Over Implicit: Users should understand how dependencies flow
- Orthogonal Concerns: Separate creation (creational) from decoration (chainable)
- Type Safety Without Reflection: Use concepts instead of runtime type information
Performance Characteristics
- Compile Time: Configuration processing and type checking happen at compile time. Larger configurations may increase build times, but this is a one-time cost.
- Runtime: Container construction and resolution are typically constexpr-compatible, allowing compilation of the entire DI pipeline into static initializers.
- Binary Size: Template instantiation may increase binary size for complex configurations, but patterns (like Pack operations) are highly optimizable.
Advanced Topics
Custom Configurations
Implement your own configs by satisfying CreationalConfig or ChainableConfig concepts:
struct MyCustomConfig {
using RelatedKey = MyKey;
using RelatedEntity = MyType;
static constexpr ConfigType CONFIG_TYPE = ConfigType::CREATIONAL;
};
Pack-Based Type Manipulation
The utilities in capymeta/algorithms/pack/ enable compile-time type filtering and rebinding:
using AllConfigs = Pack<ConfigA, ConfigB, ConfigC>;
using CreationalOnly = meta::filter_t<AllConfigs, IsCreationalConfig>;
using Dispatchers = meta::rebind_pack_t<CreationalOnly, CreationalConfigDispatcher>;
Key Modules
Frequently Asked Questions
Q: Why concepts instead of virtual functions?
A: Concepts enforce constraints at compile time without runtime overhead. They enable the same expressiveness as runtime polymorphism with zero cost.
Q: Can I mix runtime and compile-time DI?
A: Not directly. Capydi is optimized for the compile-time case where the entire dependency graph is known. For hybrid approaches, consider factory functions that accept runtime parameters.
Q: How do I handle optional dependencies?
A: Use std::optional<Reference<T>> or wrap resolution in std::expected, then check for presence before use.
See Also
License
Capydi is released under the MIT License. See LICENSE.md for details.
Author & Contributors
Created by the Capydi team. Contributions are welcome!