liant is a C++ Dependency Injection (DI) container library. It simplifies object lifecycle management and dependency resolution, allowing for cleaner, more maintainable code.
Beyond its core DI features, liant also serves as a good example of how to build, distribute, and consume a C++ library using modern CMake, including C++20 Modules and C++23 import std integration.
import std;
import liant;
struct Interface1 {};
struct Type1 : Interface1 {};
struct Type2 {
// injection via ctor ('liant::ContainerView<...>' should be first argument)
Type2(liant::ContainerView<Interface1> di)
: di(di) {}
liant::ContainerView<Interface1> di;
};
struct Type3 {
// injection via aggregate initialization ('liant::ContainerView<...>' should be first field)
liant::ContainerView<Interface1, Type2> di;
std::string arg;
void postCreate() const { std::println("Type3::postCreate"); }
void preDestroy() const { std::println("Type3::preDestroy"); }
};
int main()
{
auto container = liant::makeContainer(
liant::registerInstanceOf<Type1>().as<Interface1>(),
liant::registerInstanceOf<Type2>(),
liant::registerInstanceOf<Type3>().bindArgs("Hello, World!")
);
// instantiate all at once automatically
container->resolveAll();
liant::SharedPtr<Type3> type3Instance = container->find<Type3>();
std::println("Type3::arg: {0}", type3Instance->arg);
}- Injection through constructors or aggregate initialization.
- Detects circular dependencies at compile time.
- Dependencies may be resolved automatically all at once or lazily upon request.
- Initialization and destruction order management.
postCreate/preDestroycustomization points. - You can "include" one container (or its view/slice) as a base for another, so that dependencies from base container are reused by child container.
- Bind concrete types to interfaces or simply register types as-is.
- Multiple Distribution Formats:
- Header-only version: For traditional
#include-based usage. - C++20 Module version:
import liantsupport (standard library goes into the Global Module Fragment). - C++23 Module version:
import stdsupport.
- Header-only version: For traditional
- Nice Compile-Time Diagnostics: provides clear error messages to help you quickly resolve compilation errors if you misuse API.
std::shared_ptr<liant::Container<long-list-of-types>> container = liant::makeContainer(...)- make DI containerliant::ContainerView<Ts...>- subset of Container's dependencies- holds non-owning reference to the original erased
liant::Container - all dependencies are resolved automatically
- your best choice if a dependency registered within the Container itself depends on other dependencies also managed by the Container
- holds non-owning reference to the original erased
liant::ContainerSlice<Ts...>- subset of Container's dependencies- holds
shared_ptrto the original erasedliant::Container(i.e. it extends the lifetime of the Container and managed dependencies) - all dependencies are resolved automatically
- holds
liant::ContainerViewLazy<Ts...>- subset of Container's dependencies- holds non-owning reference to the original erased
liant::Container - dependencies should be resolved manually using
view.resolveAll()/view.resolve<T>()
- holds non-owning reference to the original erased
liant::ContainerSliceLazy<Ts...>- subset of Container's dependencies- holds
shared_ptrto the original erasedliant::Container(i.e. it extends the lifetime of the Container and managed dependencies) - dependencies should be resolved manually using
slice.resolveAll()/slice.resolve<T>()
- holds
liant::ContainerSliceWeak<Ts...>- subset of Container's dependencies- holds
weak_ptrto the original erasedliant::Container(i.e. it DOES NOT extend the lifetime of the Container and managed dependencies) - all dependencies are resolved automatically
- holds
liant::ContainerSliceWeakLazy<Ts...>- subset of Container's dependencies- holds
weak_ptrto the original erasedliant::Container(i.e. it DOES NOT extend the lifetime of the Container and managed dependencies) - dependencies should be resolved manually using
slice.resolveAll()/slice.resolve<T>()
- holds
- Git
- CMake 4.0.3
- Ninja 1.11+
- C++ toolchain:
- C++23
liant_moduletarget (withimport std)- Clang20 + libc++-20 - you will need to configure your cmake build with libc++ standard library like
-DCMAKE_CXX_FLAGS="-stdlib=libc++" - MSVCv19.38+ - works out of the box (MS VS or CLion may use outdated CMake so make sure you use the newest CMake)
- Clang20 + libc++-20 - you will need to configure your cmake build with libc++ standard library like
- C++20 header-only
lianttarget &liant_module_nostdtarget should work with any reasonably conforming C++20 compiler (GCC 13+, Clang 18+, MSVC 17.10+)
- C++23
We will use llvm-20 toolchain and CMake 4.0.3
-
Clone the repository:
git clone https://github.com/Katkevich/liant.git cd liant -
Create a build directory and configure CMake:
mkdir build cd build # -DCMAKE_CXX_FLAGS="-stdlib=libc++" - this one is neccesary here if you want C++23 'import std' support # -DCMAKE_INSTALL_PREFIX=../install - set the installation directory to 'liant/install' for test purposes (don't pollute /usr/local while testing all of this) cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DCMAKE_CXX_FLAGS="-stdlib=libc++" -DCMAKE_INSTALL_PREFIX=../install
-
Build the library:
cmake --build . -
Install the library:
cmake --install .
Installation is done. Now you can find liant package using CMake find_package command in config mode.
liant package provides 3 targets:
liant::liant- traditional header-only libraryliant::liant_module- C++23 module withimport stdsupportliant::liant_module_nostd- C++20 module with standard library being in Global Module Fragment.
You can choose which one to link against based on your project's needs.
-
Create
liant_examplefolder next to theliantlibrary folder as follows:├── liant │ ├── build │ ├── install | ├── ... | ├── CMakeLists.txt | ├── liant_example | ├── build | ├── main.cpp | ├── CMakeLists.txt -
Create liant_example/main.cpp (copy Quick Example code) and liant_example/CMakeLists.txt:
- Since
import stdis still experimental feature in CMake, we must enable it explicitly - Since our target is using standard module, we must set
CXX_MODULE_STDproperty for our target CXX_EXTENSIONSshould beONbecause of these CMake issues:- https://gitlab.kitware.com/cmake/cmake/-/issues/25916
- https://gitlab.kitware.com/cmake/cmake/-/issues/25539
cmake_minimum_required(VERSION 3.30) # CMake support for 'import std' is experimental still # To enable it you must set 'CMAKE_EXPERIMENTAL_CXX_IMPORT_STD' UUID # Each CMake version may have it's own unique UUID for each experimental feature # # CMake 4.0.3: "d0edc3af-4c50-42ea-a356-e2862fe7a444" <<< we are using this one # CMake 3.31.8: "d0edc3af-4c50-42ea-a356-e2862fe7a444" # # to find UUID for your CMake version look your version CMake sources (/Help/dev/experimental.rst) set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444") project(liant_example) # Explicitly specify the liant library installation path (path to the `liant-config.cmake` file to be more precise) # # If you'd installed the library in the default location (hadn't specified `-DCMAKE_INSTALL_PREFIX=../install` during CMake configuration) then you would not need to explicitly specify the path to the library here. # `find_package(liant CONFIG REQUIRED)` would just be enough find_package(liant CONFIG REQUIRED PATHS ../liant/install/lib/cmake/liant) add_executable(liant_example) target_compile_features(liant_example PUBLIC cxx_std_23) set_target_properties(liant_example PROPERTIES CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS ON CXX_MODULE_STD ON ) target_sources(liant_example PRIVATE main.cpp) # link with C++23 liant_module target target_link_libraries(liant_example PRIVATE liant::liant_module)
-
Build
liant_example:cd ../../ mkdir -p liant_example/build cd liant_example/build cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DCMAKE_CXX_FLAGS="-stdlib=libc++" cmake --build .
-
Run
liant_example./liant_example # prints: # # Type3::postCreate # Type3::arg: Hello, World! # Type3::preDestroy