【问题标题】:bind make_shared with variadic template将 make_shared 与可变参数模板绑定
【发布时间】:2015-08-25 09:59:20
【问题描述】:

我正在尝试编写以下工厂类,但找不到正确的语法:

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_;
};

这就是我使用工厂的方式:

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");

这些是我在声明f1f2 时遇到的错误:

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...);
            ^

std::bind 必须使用的正确语法是什么?

【问题讨论】:

  • creator_ = std::bind(&amp;std::make_shared&lt;T, TArgs&amp;...&gt;, args...);

标签: c++ c++11 variadic-templates


【解决方案1】:

std::make_shared 声明如下:

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

因此,std::make_shared&lt;T, TArgs...&gt; 将导致一个函数采用右值引用,它不会绑定到args...。一个简单的解决方法是通过折叠引用来强制它采用左值引用:

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

另一种方法是改用 lambda,它更具可读性:

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

【讨论】:

  • 谢谢,它有效。但是,我之前已经尝试过 lambda 方法,我得到了 error: parameter packs not expanded with '...' 和 gcc 4.8.4。
  • @ChronoTrigger 您是否误写了return std::make_shared&lt;T&gt;(args); 或类似内容?它works for me.
  • 没有,我只是复制粘贴。如果我执行creator_ = [args...](){return std::make_shared&lt;T&gt;(args...);}; (error: expected ‘,’ before ‘...’ token),也会出现错误。
  • @chronotrigger 改成[&amp;] 而不是[args...]
  • @0x499602D2 坏主意,这将很快成为未定义的行为。该 lambda 超出本地范围。按照 TartainLlama 的建议使用[=],而不是[&amp;]。你甚至可以剃掉更多的字符:[=]{return std::make_shared&lt;T&gt;(args...);};
【解决方案2】:

因此,不使用绑定的最高效率 C++14 解决方案实际上很尴尬。

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)... );
    };
  }
};

这个版本有一些改进。

首先,无需指定您从以下位置创建T 的参数:

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

其次,我们有f1(),而不是f1.Create()。调用工厂显然会创建工厂创建的东西——调用命名方法只是噪音。

我们可以更进一步:

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)... )
  );
}

我们完全取消了Factory 类型——Factory&lt;T&gt; 只是成为std::function 的别名,它什么都不带,返回一个shared_ptr&lt;T&gt;

live example.


现在我觉得details::make_factory 很无聊。

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,这里我们将“从元组中调用函数”分别写成invoke

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

会稍微圆滑,但gcc 5.2.0 doesn't like it

【讨论】:

  • 如果有人在 2017 年登陆这里。template const auto shared_maker = [](auto&&...args){ return std::make_shared(decltype(args)(args) ...); };在 clang (xcode 9.2) 和 g++ 上运行良好。
猜你喜欢
  • 2023-03-16
  • 1970-01-01
  • 1970-01-01
  • 2016-01-16
  • 1970-01-01
  • 2016-12-01
  • 1970-01-01
  • 2012-08-08
  • 1970-01-01
相关资源
最近更新 更多