boost-ext / di

C++14 Dependency Injection Library
https://boost-ext.github.io/di
1.16k stars 140 forks source link

Bind to non-type template parameter #507

Open CyberDani opened 3 years ago

CyberDani commented 3 years ago

Hi Dev Team,

I am curious if non-type template parameter binding is possible or if it will be possible in future versions. I did not found anything related to this among the code samples or the documentation. I have also did not succeed to "guess it" by myself. If it is possible then I would suggest to introduce it among the other examples.

My class definition is a more complex one but the main concept is the same with this simplified one below:

Code sample

template <int T>
struct myClass
{
    int f() { return T; }
};

Example for a kind of expected code

auto injector = di::make_injector(
      di::bind<int>().to<42>()  // .to<>() meaning .toNonTypeTemplateParameter<42>()
);

auto obj = injector.create<myClass>();
assert(obj.f() == 42);

Specifications

krzysztof-jusiak commented 3 years ago

Hi @CyberDani, Thanks for the question.

Unfortunately, due to C++ not having universal templates (NTTP vs types), the exact syntax is not supported ATM. However, it works if inject and stick to the types instead.

template <class T = class Int>
struct myClass
{
    int f() { return T{}; }
};
int main() {
  const auto injector = boost::di::make_injector(
     boost::di::bind<class Int>.to<std::integral_constant<int, 42>>()
  );

  return injector.create<myClass>().f(); // returns 42
}

Full example -> https://godbolt.org/z/jqh5b5

CyberDani commented 3 years ago

Hi @krzysztof-jusiak,

Thanks a lot for suggesting me a compile-time alternative from the standard library, it is appreciated (as well as the quick answer).

I have never used std::integral_constant before so I wrote some tests (learning tests as Uncle Bob calls them in his Clean Code book) before and it worked well as expected:

template <class T = class Int>
struct MyClass
{
    static constexpr int value = T::value;
    static constexpr int value2 = T{};

    int f() { return value; }
    int f2() { return T{}; }
};

TEST_CASE("Test std::integral_constant at compile time in class templates", kLearningTestTag)
{
    MyClass<std::integral_constant<int, 45>> obj;
    static_assert(obj.value == 45, "assertion failed");
    static_assert(obj.value2 == 45, "assertion failed");

    REQUIRE(obj.f() == 45);
    REQUIRE(obj.f2() == 45);
}

I have refactored my class based on this idea along with the related unit tests. Glad to see that concepts (C++ 20) required only minor changes 😇 to keep them compatible with the refactored class. All works again and boost::di binding compiles with my class. The next tests should all work after this, so thank you again.

I would like to mention that it is kind of a tradeoff now because I choose support with boost::di over the more clear intent specified by my descriptive type within the NTTP, but I am OK with that considering that now I can trust boost::di with object creation.