Skip to content

Commit

Permalink
feat: tuple map bindings + lambda resolvers (#6)
Browse files Browse the repository at this point in the history
* feat: use variant bindings in container's symbol map

This support faster lookup speeds as we are no longer using `std::any`.

* reduce test flake across platforms

* update single_includes

* fix: windows optimize copts

* docs: remove old api

* lower the bar a bit (speed test)

* refactor: tuple >>> map + variant (for binding map)

* remove unused include

* only use container.template get... call when necessary

* add consumed std types

* fix: allow for duplicate symbol types + allow struct symbols

* docs: refer to the containerless branch

* test: use catch2 BENCHMARK

* refactor: remove resolver classes

* remove unused meta structs

* refactor: cache/constant resolver lambdas

* fix: codacy extra newline
  • Loading branch information
mosure authored Mar 21, 2021
1 parent ad8ab97 commit 5ff69ec
Show file tree
Hide file tree
Showing 35 changed files with 744 additions and 509 deletions.
4 changes: 2 additions & 2 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
build:windows --copt="/std:c++17" --copt="/W4"
build:windows --copt="/std:c++17" --copt="/W4" --copt="/O2"
build:linux --copt="-std=c++17" --copt="-O3"
build:macos --copt="-std=c++17"
build:macos --copt="-std=c++17" --copt="-O3"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@

.vscode/
bazel-*

callgrind.out.*
1 change: 1 addition & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ cc_library(
"-Wall",
"-Wextra",
"-Wshadow",
"-Wunused",
],
hdrs = glob([
"include/**/*.hpp",
Expand Down
27 changes: 20 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@

C++17 inversion of control and dependency injection container library.

## Containerless Version

See the [`containerless` branch](https://github.com/mosure/inversify-cpp/tree/containerless) for a static binding (containerless) version of the library.

## Features
* Constant, dynamic, and automatic resolvers
* Singleton, resolution (TODO), and unique scopes
Expand Down Expand Up @@ -71,6 +75,8 @@ namespace symbols {

```
> Note: symbols which hold the same interface type may do so via structs which inherit inversify::Symbol
#### Declare Classes and Dependencies
```cpp
Expand Down Expand Up @@ -135,9 +141,9 @@ Singleton scope dynamic bindings cache the first resolution of the binding.
```cpp

container.bind<symbols::fizz>().toDynamicValue(
[](const inversify::Context& ctx) {
auto foo = ctx.container.get<symbols::foo>();
auto bar = ctx.container.get<symbols::bar>();
[](auto& ctx) {
auto foo = ctx.container.template get<symbols::foo>();
auto bar = ctx.container.template get<symbols::bar>();

auto fizz = std::make_shared<Fizz>(foo, bar);

Expand All @@ -154,10 +160,10 @@ Dynamic bindings can be used to resolve factory functions.
```cpp
container.bind<symbols::fizzFactory>().toDynamicValue(
[](const inversify::Context& ctx) {
[](auto& ctx) {
return [&]() {
auto foo = ctx.container.get<symbols::foo>();
auto bar = ctx.container.get<symbols::bar>();
auto foo = ctx.container.template get<symbols::foo>();
auto bar = ctx.container.template get<symbols::bar>();
auto fizz = std::make_shared<Fizz>(foo, bar);
Expand Down Expand Up @@ -209,12 +215,19 @@ Use the following to run tests:

`bazel run test --enable_platform_specific_config`

> Note: run the example app in a similar way: `bazel run example --enable_platform_specific_config`
> Note: run the example app in a similar way: `bazel run example/simple --enable_platform_specific_config`
## TODOS
* More compile-time checks
* Resolution scope

### Profiling

Run the following to generate a callgrind file:

* `bazel run example/profiling --enable_platform_specific_config --compilation_mode=dbg -s`
* `valgrind --tool=callgrind --dump-instr=yes --simulate-cache=yes --collect-jumps=yes ./bazel-bin/example/profiling/profiling`

## Generating `single_include` Variant

Run `python ./third_party/amalgamate/amalgamate.py -c ./third_party/amalgamate/config.json -s ./` from the root of the repository.
Expand Down
13 changes: 13 additions & 0 deletions example/profiling/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("@rules_cc//cc:defs.bzl", "cc_binary")


cc_binary(
name = "profiling",
srcs = [
"main.cpp",
],
deps = [
"//:inversify",
],
visibility = ["//visibility:public"]
)
46 changes: 46 additions & 0 deletions example/profiling/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include <iostream>

#include <mosure/inversify.hpp>


namespace inversify = mosure::inversify;

using int_symbol = inversify::Symbol<int>;

struct Service {
int val;

Service() : val(0) { }
Service(int val) : val(val) { }

void foo() {
std::cout << "hello: " << val << std::endl;
}
};

template <>
struct inversify::Injectable<Service>
: inversify::Inject<
int_symbol
>
{ };

using test_symbol = inversify::Symbol<Service>;


template <typename Container>
void to_profile(Container& container) {
container.template get<test_symbol>().foo();
}

int main() {
inversify::Container<
int_symbol,
test_symbol
> container;

container.bind<int_symbol>().toConstantValue(3);
container.bind<test_symbol>().to<Service>();

to_profile(container);
}
10 changes: 5 additions & 5 deletions example/BUILD → example/simple/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")


cc_library(
name = "example_api",
name = "simple_api",
hdrs = glob([
"api/*.hpp",
"src/*.hpp",
Expand All @@ -14,7 +14,7 @@ cc_library(
)

cc_library(
name = "example_src_hdrs",
name = "simple_src_hdrs",
hdrs = glob([
"src/*.hpp",
]),
Expand All @@ -25,13 +25,13 @@ cc_library(
)

cc_binary(
name = "example",
name = "simple",
srcs = glob([
"src/*.cpp",
]),
deps = [
":example_api",
":example_src_hdrs",
":simple_api",
":simple_src_hdrs",
"//:inversify",
],
visibility = ["//visibility:public"]
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
6 changes: 5 additions & 1 deletion example/src/main.cpp → example/simple/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
namespace inversify = mosure::inversify;

int main() {
inversify::Container container;
inversify::Container<
symbols::logger,
symbols::service,
symbols::settings
> container;

container.bind<symbols::logger>().to<Logger>().inSingletonScope();
container.bind<symbols::service>().to<Service>();
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
55 changes: 36 additions & 19 deletions include/mosure/binding.hpp
Original file line number Diff line number Diff line change
@@ -1,56 +1,73 @@
#pragma once

#include <functional>
#include <memory>
#include <type_traits>

#include <mosure/context.hpp>
#include <mosure/factory.hpp>
#include <mosure/resolver.hpp>
#include <mosure/exceptions/resolution.hpp>


namespace mosure::inversify {

template <typename T>
template <typename T, typename... SymbolTypes>
class BindingScope {
public:
void inSingletonScope() {
resolver_ = std::make_shared<inversify::CachedResolver<T>>(resolver_);
static_assert(
std::is_copy_constructible<T>::value,
"inversify::BindingScope singleton must have copy constructor"
);

this->factory_ = [this, factory = std::move(factory_)](auto& context) {
if (!this->cached_set_) {
this->cached_ = factory(context);
this->cached_set_ = true;
}

return this->cached_;
};
}

protected:
inversify::ResolverPtr<T> resolver_;
T cached_;
bool cached_set_ { false };
inversify::Factory<T, SymbolTypes...> factory_;
};

template <typename T>
class BindingTo : public BindingScope<T> {
template <typename T, typename... SymbolTypes>
class BindingTo
: public BindingScope<T, SymbolTypes...>
{
public:
void toConstantValue(T&& value) {
this->resolver_ = std::make_shared<inversify::ConstantResolver<T>>(value);
this->factory_ = [val = std::move(value)](auto&) {
return val;
};
}

BindingScope<T>& toDynamicValue(inversify::Factory<T> factory) {
this->resolver_ = std::make_shared<inversify::DynamicResolver<T>>(factory);
BindingScope<T, SymbolTypes...>& toDynamicValue(inversify::Factory<T, SymbolTypes...>&& factory) {
this->factory_ = std::move(factory);

return *this;
}

template <typename U>
BindingScope<T>& to() {
this->resolver_ = std::make_shared<inversify::AutoResolver<T, U>>();
BindingScope<T, SymbolTypes...>& to() {
this->factory_ = inversify::get_auto_resolver<T, U, SymbolTypes...>();

return *this;
}
};

template <typename T>
class Binding : public BindingTo<T> {
template <typename T, typename... SymbolTypes>
class Binding
: public BindingTo<typename T::value, SymbolTypes...>
{
public:
inline T resolve(const Context& context) const {
if (!this->resolver_) {
throw inversify::exceptions::ResolutionException("inversify::Resolver not found. Malformed binding.");
}

return this->resolver_->resolve(context);
inline typename T::value resolve(const inversify::Context<SymbolTypes...>& context) {
return this->factory_(context);
}
};

Expand Down
55 changes: 44 additions & 11 deletions include/mosure/container.hpp
Original file line number Diff line number Diff line change
@@ -1,31 +1,64 @@
#pragma once

#include <tuple>

#include <mosure/binding.hpp>
#include <mosure/context.hpp>
#include <mosure/interfaces/icontainer.hpp>
#include <mosure/meta.hpp>


namespace mosure::inversify {

template <typename Symbol>
struct BindingLookup {
inline static inversify::Binding<typename Symbol::value> binding {};
};

class Container : public inversify::IContainer<Container> {
template <typename... SymbolTypes>
class Container
: public inversify::IContainer<Container, SymbolTypes...>
{
public:
static_assert(
meta::valid_symbol_types_v<SymbolTypes...>,
"inversify::Container symbols must be of type inversify::Symbol"
);

static_assert(
!meta::is_empty_v<SymbolTypes...>,
"inversify::Container must register at least one symbol"
);

using BindingMap = std::tuple<
inversify::Binding<
SymbolTypes,
SymbolTypes...
>...
>;

template <typename T>
inline inversify::BindingTo<typename T::value>& bind() {
return BindingLookup<T>::binding;
inline inversify::BindingTo<typename T::value, SymbolTypes...>& bind() {
static_assert(
meta::contains_v<T, SymbolTypes...>,
"inversify::Container symbol not registered"
);

return std::get<
inversify::Binding<T, SymbolTypes...>
>(bindings_);
}

template <typename T>
inline typename T::value get() const {
return BindingLookup<T>::binding.resolve(context_);
inline typename T::value get() {
static_assert(
meta::contains_v<T, SymbolTypes...>,
"inversify::Container symbol not registered"
);

return std::get<
inversify::Binding<T, SymbolTypes...>
>(bindings_).resolve(context_);
}

private:
inversify::Context context_ { *this };
BindingMap bindings_ {};
inversify::Context<SymbolTypes...> context_ { *this };
};

}
4 changes: 3 additions & 1 deletion include/mosure/context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@

namespace mosure::inversify {

template <typename... SymbolTypes>
class Container;

template <typename... SymbolTypes>
struct Context {
const inversify::IContainer<Container>& container;
inversify::IContainer<Container, SymbolTypes...>& container;
};

}
4 changes: 2 additions & 2 deletions include/mosure/factory.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace mosure::inversify {

template <typename T>
using Factory = std::function<T(const inversify::Context&)>;
template <typename T, typename... SymbolTypes>
using Factory = std::function<T(const inversify::Context<SymbolTypes...>&)>;

}
Loading

0 comments on commit 5ff69ec

Please # to comment.