boost-ext / di

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

Injecting vector<string> into a class #424

Closed jamespharvey20 closed 4 years ago

jamespharvey20 commented 5 years ago

Expected Behavior

To be able to inject into a constructor taking a vector<string>.

Actual Behavior

Fails compilation, or constructs an empty vector.

Steps to Reproduce the Problem

For vector<int>, this example works:

#include <vector>
using namespace std;

#include "boost/di.hpp"
using namespace di = boost::di;

struct example {
   example(const vector<int> i) {
      for(auto x : i) {
         cout << x << endl;
      }
   }
};

int main() {
   auto injector = di::make_injector(
      di::bind<int[]>.to({123, 456, 789})
   );
   injector.create<example>();
}

Notably, it has to be done that way as shown in your examples. di::bind<vector<int>> causes a compilation failure. (Bonus question: is there a way to specify vector<T> rather than T[]?)

And, as expected the above example, prints:

123
456
789

But, I'm having trouble getting this to work with a constructor taking vector<string>:

#include <string>
#include <vector>
using namespace std;

#include "boost/di.hpp"
using namespace di = boost::di;

struct example {
   example(const vector<string> i) {
      for(auto x : i) {
         cout << x << endl;
      }
   }
};

int main() {
   auto injector = di::make_injector(
      ???
   );
   injector.create<example>();
}

If I try this:

      di::bind<string[]>.to({"123", "456", "789"})

I get these compilation and linker errors:

boost/di.hpp:2770:3: note: declared here
   create
   ^~~~~~
boost/di.hpp: At global scope:
boost/di.hpp:1199:2: warning: inline function ‘static To boost::di::v1_1_0::concepts::scoped<bo
ost::di::v1_1_0::scopes::instance, T>::is_not_convertible_to<To>::error(boost::di::v1_1_0::_) [with To = std::vector<std::__cxx11::basic_string<char> >; T = std::initializer_list<
const char*>]’ used but never defined
  error(_ = "instance is not convertible to the requested type, verify binding: 'di::bind<T>.to(value)'?");
  ^~~~~
[100%] Linking CXX executable example
/usr/bin/ld: example.cpp.o: in function `boost::di::v1_1_0::concepts::scoped<boost::di::v1_1_0::scopes::instance, std::initializer_list<char const*> >::is_not_convertible_to<std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std:
:allocator<char> > > > >::operator std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >() const':
boost/di.hpp:1195: undefined reference to `boost::di::v1_1_0::concepts::scoped<boost::di::v1_1_
0::scopes::instance, std::initializer_list<char const*> >::is_not_convertible_to<std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::error(boost::di::v1_1_0::_)'

If I try this:

   vector<string> strings{"123", "456", "789"};
...
      di::bind<string[]>.to(strings)

Then it compiles, but prints nothing, because for some reason the constructed has size 0.

Same with:

   string strs[] = {"123", "456", "789"};

If I try this:

      di::bind<char*[]>.to({"123", "456", "789"})

I get this compilation error:

boost/di.hpp: In instantiation of ‘struct boost::di::v1_1_0::aux::concept_check<boost::di::v1_1
_0::concepts::type_<const char*>::is_not_related_to<char> >’:
boost/di.hpp:1712:83:   required by substitution of ‘template<class T, typename boost::di::v1_1_0::aux::concept_check<typename boost::di::v1_1_0::concepts::boundable__<char* [], T>::type>::type <anonymous> > auto boost::di::v1_1_0::core::dependency<boost::di::v1_1_0::scopes::deduce, char* [], char* [], boost::di::v1_1_0::no_name, void, boost::di::v1_1_0::core::none>::to<T, <enumerator> >(std::initializer_list<_Tp>) [with T = const char*; typename boost::di::v1_1_0::aux::concept_check<typename boost::di::v1_1_0::concepts::boundable__<char* [], T>::type>::type <anonymous> = <missing>]’
src/example.cpp:52:49:   required from here
boost/di.hpp:368:17: error: static assertion failed: constraint not satisfied
   static_assert(T::value, "constraint not satisfied");
                 ^
src/example.cpp: In function ‘int main()’:
src/example.cpp:52:49: error: invalid use of brace-enclosed initializer list
       di::bind<char*[]>.to({"123", "456", "789"})
                                                 ^
src/example.cpp:65:27: error: expected primary-expression before ‘>’ token
    injector.create<example>();
                           ^
src/example.cpp:65:29: error: expected primary-expression before ‘)’ token
    injector.create<example>();
                             ^

If I split it to:

   char* list[] = {"123", "456", "789"};
   ...
      di::bind<char*[]>.to(list)

Then it compiles, but prints nothing, because for some reason the constructed has size 0. (With compilation, using -Wwrite-strings, I get a warning: ISO C++ forbids converting a string constant to 'char*' because the string literals should be const, so I'm looking for a solution that doesn't trigger this warning anyway.)

If I try:

      di::bind<vector<string>>.to({"123", "456", "789"})
      -or-
      di::bind<vector<string>>.to(vector<string>{"123", "456", "789"})

Then I get: (basically the same error as if I try vector<int> in the original example.

boost/di.hpp: In instantiation of ‘struct boost::di::v1_1_0::aux::concept_check<boost::di::v1_1_0::concepts::type_<std::vector<std::__cxx11::basic_string<char> > >::has_disallowed_qualifiers>’:
boost/di.hpp:1987:9:   required from here
boost/di.hpp:368:17: error: static assertion failed: constraint not satisfied
   static_assert(T::value, "constraint not satisfied");
                 ^
boost/di.hpp: At global scope:
boost/di.hpp:1987:9: error: no type named ‘type’ in ‘struct boost::di::v1_1_0::aux::concept_check<boost::di::v1_1_0::concepts::type_<std::vector<std::__cxx11::basic_string<char> > >::has_disallowed_qualifiers>’
         bind
         ^~~~
src/example.cpp: In function ‘int main()’:
src/example.cpp:43:27: error: expected primary-expression before ‘>’ token
    injector.create<example>();
                           ^
src/example.cpp:43:29: error: expected primary-expression before ‘)’ token
    injector.create<example>();
                             ^

If I try:

struct Name {
   string name;
};
...
   Name list[] = {"123", "456", "789"};
...
      di::bind<Name[]>.to(list)

Then it still constructs an empty vector. (I have verified in gdb that I can print list and properly see its contents.)

Also, if I mess up and miss the braces:

     di::bind<char*[]>.to("123", "456", "789")

I get this linker error. Maybe there's an improvement here, maybe that's just on me for missing the braces:

src/example.cpp:56: undefined reference to `boost::di::v1_1_0::core::dependency<boost::di::v1_1_0::scopes::deduce, char* [], char* [], boost::di::v1_1_0::no_name, void, boost::di::v1_1_0::core::none>& boost::di::v1_1_0::core::dependency<boost::di::v1_1_0::scopes::deduce, char* [], char* [], boost::di::v1_1_0::no_name, void, boost::di::v1_1_0::core::none>::to<>(...) const'

I've tried about 50 other things, and won't bother you with posting all the errors and what went wrong with them. Haven't been able to find one that works.

Specifications

jamespharvey20 commented 5 years ago

Found that this works:

di::bind<string[]>.to<string>({"123", "456", "789"})

I'm leaving this open, because I don't know if any of the things I originally posted should work, or if there should be more clear errors given. Also, because I don't know if the solution I found is the best/minimal way to do this.

It would be nice to have an in-depth explanation of the make_injector and bind syntax, in a way as close as possible to "explain it to me like I'm 5". Occasionally I wind up stuck trying a bunch of things until it works, and would love to have the knowledge to just know how to write it correctly.

I've seen https://boost-experimental.github.io/di/user_guide/index.html#di_make_injector and https://boost-experimental.github.io/di/user_guide/index.html#di_bind but don't have a great grasp on things like these:

kanstantsin-chernik commented 4 years ago

You can also try di::bind<string[]>.to<string>({"123"s, "456"s, "789"s}). Ultimately, "123" has const char[4] type and not string. You need to tell DI what you mean by this binding explicitly.

Bonus question: is there a way to specify vector rather than T[]? No you cannot. But it is just an implementation detail. You shouldn't worry about it being an array rather than a vector