如何绑定传递未指定调用包装器的成员函数模板

How do I bind a member function template passing unspecified call wrapper

本文关键字:包装 调用 函数模板 成员 未指定 何绑定 绑定      更新时间:2023-10-16

我尝试使用VC11和g++ 4.7.2编译以下示例:

#include <functional>
class X {
public:
  template <typename T>
  explicit X(T t)
  {
    std::bind(&X::invoke<T>, this, t)();
  }
private:
  template <typename T>
  void invoke(T t)
  {
    t();
  }
};
class Y {
  public:
    void foo() {
      //...
    }
};

int main() {
  Y y;
  X x(std::bind(&Y::foo, &y));
  return 0;
}

但是它以错误结束。我不确定粘贴整个编译器输出是否合理,但通常

vc11说:

错误C2664: 'void std::_Pmf_wrap::operator ()(_Farg0 &,_V0_t) const':无法将参数3从'void'转换为'std::_Bind,Y *,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil,std::_Nil>' c:program files (x86)microsoft visual studio 11.0vcincludefunctional 1152 1 ConsoleApplication1 (microsoft visual c++ Compiler Nov 2012 CTP)

和g + +:

编译结束时出现错误:
source.cpp:在实例化'X::X(T) [with T = std::_Bind(Y*)>]':
Source.cpp:28:33: required from here
source.cpp: 9:错误:无法与调用的(std:: _Bind_helper (Y *)>), X * const std:: _Bind (Y *)>,>::类型{aka std:: _Bind (Y *)>)> (X *, std:: _Bind (Y *)>)>}) ()'

有办法解决这个问题吗?这对我来说非常重要,保存主要思想-一个可以用任何可调用对象(函数对象,函数指针或std::bind()函数返回的调用包装器)实例化的类。

如果有人帮忙,我将非常感激。

注:如果我创建X的实例,传递函数对象或函数指针,它会编译

我认为他们在将boost::bind引入std::bind时遗漏了一个重要的部分,即boost::protect()。您的代码可以像下面这样修复:

#include <boost/bind/protect.hpp>
// ...
X x(boost::protect(std::bind(&Y::foo, &y)));

,或者另外:

template <typename T>
explicit X(T t)
{
    auto tt = boost::protect(t);
    auto f = std::bind(&X::invoke<decltype(tt)>, this, tt);
    f();
}

见http://www.boost.org/doc/libs/1_53_0/libs/bind/bind.html

虽然默认情况下第一个参数不被求值,但所有其他参数都被求值。有时不需要对第一个参数之后的参数求值,即使它们是嵌套的绑定子表达式。这可以在另一个函数对象protect的帮助下实现,它掩盖了类型,这样bind就不能识别和计算它。当被调用时,protect只是将参数列表不加修改地转发给另一个函数对象。

头文件boost/bind/protect.hpp包含了一个protect的实现。要保护bind函数对象不被求值,可以使用protect(bind(f,…))。


Scott Meyers即将出版的Effective c++ 11: Content and Status将推荐使用lambdas而不是std::bind。在c++ 11中,您可以简单地这样做:

template <typename T>
explicit X(T t)
{
    auto f = [t, this]() { this->invoke(t); };
    f();
}
// ...
X x([&y](){ y.foo(); });

问题的根本原因似乎是std::bind对其参数的内部复制,特别是对t的引用。

你可以这样处理:

  template <typename T>
  explicit X(T t)
  {
      std::bind(&X::invoke<T>, this, std::placeholders::_1)(t);
      //                             ^^^^^^^^^^^^^^^^^^^^^  ^
  }

这也可以,但不允许使bind的结果比参数t的时间长(否则,您将向invoke<T>()传递一个悬空引用):

  template <typename T>
  explicit X(T t)
  {
      std::bind(&X::invoke<T>, this, cref(t))();
      //                             ^^^^^^^
  }

更新:

上面的解决方案是可以帮助您实现示例中所显示的内容的变通方法。然而,从评论中可以看出,您的用例可能会有很大的不同(例如,将bind的结果传递给以后的评估)。

正如Maxim Yegorushkin在他的回答中正确指出的那样,概念上正确的解决方案包括使用Boost的protect这样的结构。

如果你不想使用Boost,使用c++ 11的可变模板来定义你自己的protect()函数是很容易的:

// Imitates boost::protect, but with variadic templates and perfect forwarding
namespace detail
{
    template<typename F>
    struct protect
    {
    private:
        F _f;
    public:
        explicit protect(F f): _f(f)
        {
        }
        template<typename... Ts>
        auto operator () (Ts&&... args) -> 
            decltype(_f(std::forward<Ts>(args)...))
        {
            return _f(std::forward<Ts>(args)...);
        }
    };
}
template<typename F>
detail::protect<F> protect(F&& f)
{
    return detail::protect<F>(std::forward<F>(f));
}

最终,这是你在课堂上如何使用它,正如Maxim建议的:

class X
{
public:
    template <typename T>
    explicit X(T t)
    {
        auto pt = protect(t);
        std::bind(&X::invoke<decltype(pt)>, this, pt)();
    }
private:
    template <typename T>
    void invoke(T t)
    {
        t();
    }
};