bind make_shared with variadic template

霸气de小男生 提交于 2019-12-10 09:35:34

问题


I'm trying to write the following factory class, but I can't find the proper syntax:

template<class T, typename... TArgs>
class Factory {
 public:
  Factory(TArgs... args) {
    creator_ = std::bind(&std::make_shared<T, TArgs...>, args...);
    //      ^^^ some error around here
  }
  std::shared_ptr<T> Create() const {
    return creator_();
  }
 private:
  std::function<std::shared_ptr<T>()> creator_;
};

This is how I use the factory:

class Foo {
 public:
  Foo(bool value) {}
};
class Bar {
 public:
   Bar(const std::string& value) {}
};
Factory<Foo, bool> f1(true);
Factory<Bar, std::string> f2("string");

These are the errors I get when declaring f1 and f2:

error: no match for 'operator=' (operand types are 'std::function<std::shared_ptr<Foo>()>' and 'std::_Bind_helper<false, std::shared_ptr<Foo> (*)(bool&&), bool&>::type {aka std::_Bind<std::shared_ptr<Foo> (*(bool))(bool&&)>}')
   creator_ = std::bind(&std::make_shared<T, TArgs...>, args...);
            ^

error: no match for 'operator=' (operand types are 'std::function<std::shared_ptr<Bar>()>' and 'std::_Bind_helper<false, std::shared_ptr<Bar> (*)(std::basic_string<char>&&), std::basic_string<char, std::char_traits<char>, std::allocator<char> >&>::type {aka std::_Bind<std::shared_ptr<Bar> (*(std::basic_string<char>))(std::basic_string<char>&&)>}')
   creator_ = std::bind(&std::make_shared<T, TArgs...>, args...);
            ^

What is the correct syntax I must use with std::bind?


回答1:


std::make_shared is declared like this:

template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );

As such, std::make_shared<T, TArgs...> will result in a function taking rvalue references, which won't bind to args.... A simple fix for this is to force it to take lvalue references by collapsing the reference:

creator_ = std::bind(&std::make_shared<T,TArgs&...>, args...);
//                                            ^

An alternative is to use a lambda instead, which is more readable:

creator_ = [=](){return std::make_shared<T>(args...);};



回答2:


So, a maximally efficient C++14 solution that doesn't use bind is actually awkward.

template<class T>
struct Factory {
  template<class...Args>
  Factory(Args&&... args):
    creator_(
      make_creator(
        std::index_sequence_for<Args...>{},
        std::make_tuple( std::forward<Args>(args)...
      )
    )
  {}
  std::shared_ptr<T> operator()() const {
    return creator_();
  }
private:
  using signature = std::shared_ptr<T>();
  using creator = std::function<signature>;
  creator creator_;
  // helper, to make a lambda with a tuple to unpack:
  template<class Tup, size_t...Is>
  static creator make_creator(std::index_sequence<Is...>, Tup&& tup) {
    return [tup = std::forward<Tup>(tup)]{
      return std::make_shared<T>( std::get<Is>(tup)... );
    };
  }
};

this version has a few improvements.

First, no need to specify arguments you create the T from:

Factory<Foo> f1(true);
Factory<Bar> f2("string");

Second, instead of f1.Create(), we have f1(). Invoking a factory clearly creates the thing the factory creates -- calling a named method is just noise.

We could go a step further:

template<class T>
using Factory = std::function<std::shared_ptr<T>()>;

namespace details {
  template<class T, class Tup, size_t...Is>
  Factory<T> make_factory(std::index_sequence<Is...>, Tup&& tup) {
    return [tup = std::forward<Tup>(tup)]{
      return std::make_shared<T>( std::get<Is>(tup)... );
    };
  }
}
template<class T, class...Args>
Factory<T> make_factory(Args&&...args) {
  return details::make_factory<T>(
    std::index_sequence_for<Args...>{},
    std::make_tuple( std::forward<Args>(args)... )
  );
}

where we do away with the Factory type entirely -- Factory<T> just becomes an alias for a std::function that takes nothing and returns a shared_ptr<T>.

live example.


Now I find details::make_factory to be boring.

namespace details {
  template<class F, class Tup, size_t...Is>
  auto invoke( F&& f, Tup&& tup, std::index_sequence<Is...> )
  -> std::result_of_t<F( std::tuple_element_t<Is, std::decay_t<Tup>>... )>
  {
    return std::forward<F>(f)( std::get<Is>(std::forward<Tup>(tup))... );
  }
}
template<class F, class Tup, size_t...Is>
auto invoke( F&& f, Tup&& tup )
{
  using count = std::tuple_size< std::decay_t<Tup> >;
  using indexes = std::make_index_sequence< count{} >;

  return details::invoke(
    std::forward<F>(f),
    std::forward<Tup>(tup),
    indexes{}
  );
}
template<class T>
auto shared_maker() {
  return [](auto&&...args){
    return std::make_shared<T>( decltype(args)(args)... );
  };
}

template<class T, class...Args>
Factory<T> make_factory(Args&&...args) {
  return [tup=std::make_tuple(std::forward<Args>(args)...)]{
    return invoke(
      shared_maker<T>(),
      tup
    );
  };
}

live example, where we take the 'invoke a function from a tuple' and write it as invoke separately.

template<class T>
const auto shared_maker = [](auto&&...args){
  return std::make_shared<T>(decltype(args)(args)...);
};

would be slightly slicker, but gcc 5.2.0 doesn't like it.



来源:https://stackoverflow.com/questions/32200981/bind-make-shared-with-variadic-template

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!